refactor: rename the atom directory to shell
This commit is contained in:
parent
4575a4aae3
commit
d7f07e8a80
631 changed files with 0 additions and 0 deletions
104
shell/browser/ui/accelerator_util.cc
Normal file
104
shell/browser/ui/accelerator_util.cc
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/accelerator_util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/keyboard_util.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
bool StringToAccelerator(const std::string& shortcut,
|
||||
ui::Accelerator* accelerator) {
|
||||
if (!base::IsStringASCII(shortcut)) {
|
||||
LOG(ERROR) << "The accelerator string can only contain ASCII characters";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> tokens = base::SplitString(
|
||||
shortcut, "+", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
|
||||
|
||||
// Now, parse it into an accelerator.
|
||||
int modifiers = ui::EF_NONE;
|
||||
ui::KeyboardCode key = ui::VKEY_UNKNOWN;
|
||||
for (const auto& token : tokens) {
|
||||
bool shifted = false;
|
||||
ui::KeyboardCode code = atom::KeyboardCodeFromStr(token, &shifted);
|
||||
if (shifted)
|
||||
modifiers |= ui::EF_SHIFT_DOWN;
|
||||
switch (code) {
|
||||
// The token can be a modifier.
|
||||
case ui::VKEY_SHIFT:
|
||||
modifiers |= ui::EF_SHIFT_DOWN;
|
||||
break;
|
||||
case ui::VKEY_CONTROL:
|
||||
modifiers |= ui::EF_CONTROL_DOWN;
|
||||
break;
|
||||
case ui::VKEY_MENU:
|
||||
modifiers |= ui::EF_ALT_DOWN;
|
||||
break;
|
||||
case ui::VKEY_COMMAND:
|
||||
modifiers |= ui::EF_COMMAND_DOWN;
|
||||
break;
|
||||
case ui::VKEY_ALTGR:
|
||||
modifiers |= ui::EF_ALTGR_DOWN;
|
||||
break;
|
||||
// Or it is a normal key.
|
||||
default:
|
||||
key = code;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == ui::VKEY_UNKNOWN) {
|
||||
LOG(WARNING) << shortcut << " doesn't contain a valid key";
|
||||
return false;
|
||||
}
|
||||
|
||||
*accelerator = ui::Accelerator(key, modifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenerateAcceleratorTable(AcceleratorTable* table,
|
||||
atom::AtomMenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
atom::AtomMenuModel::ItemType type = model->GetTypeAt(i);
|
||||
if (type == atom::AtomMenuModel::TYPE_SUBMENU) {
|
||||
auto* submodel = model->GetSubmenuModelAt(i);
|
||||
GenerateAcceleratorTable(table, submodel);
|
||||
} else {
|
||||
ui::Accelerator accelerator;
|
||||
if (model->ShouldRegisterAcceleratorAt(i)) {
|
||||
if (model->GetAcceleratorAtWithParams(i, true, &accelerator)) {
|
||||
MenuItem item = {i, model};
|
||||
(*table)[accelerator] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
const ui::Accelerator& accelerator) {
|
||||
if (base::ContainsKey(*table, accelerator)) {
|
||||
const accelerator_util::MenuItem& item = (*table)[accelerator];
|
||||
if (item.model->IsEnabledAt(item.position)) {
|
||||
const auto event_flags =
|
||||
accelerator.MaskOutKeyEventFlags(accelerator.modifiers());
|
||||
item.model->ActivatedAt(item.position, event_flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace accelerator_util
|
36
shell/browser/ui/accelerator_util.h
Normal file
36
shell/browser/ui/accelerator_util.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_ACCELERATOR_UTIL_H_
|
||||
#define ATOM_BROWSER_UI_ACCELERATOR_UTIL_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
typedef struct {
|
||||
int position;
|
||||
atom::AtomMenuModel* model;
|
||||
} MenuItem;
|
||||
typedef std::map<ui::Accelerator, MenuItem> AcceleratorTable;
|
||||
|
||||
// Parse a string as an accelerator.
|
||||
bool StringToAccelerator(const std::string& description,
|
||||
ui::Accelerator* accelerator);
|
||||
|
||||
// Generate a table that contains memu model's accelerators and command ids.
|
||||
void GenerateAcceleratorTable(AcceleratorTable* table,
|
||||
atom::AtomMenuModel* model);
|
||||
|
||||
// Trigger command from the accelerators table.
|
||||
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
const ui::Accelerator& accelerator);
|
||||
|
||||
} // namespace accelerator_util
|
||||
|
||||
#endif // ATOM_BROWSER_UI_ACCELERATOR_UTIL_H_
|
80
shell/browser/ui/atom_menu_model.cc
Normal file
80
shell/browser/ui/atom_menu_model.cc
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2015 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
|
||||
#include "base/stl_util.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
bool AtomMenuModel::Delegate::GetAcceleratorForCommandId(
|
||||
int command_id,
|
||||
ui::Accelerator* accelerator) const {
|
||||
return GetAcceleratorForCommandIdWithParams(command_id, false, accelerator);
|
||||
}
|
||||
|
||||
AtomMenuModel::AtomMenuModel(Delegate* delegate)
|
||||
: ui::SimpleMenuModel(delegate), delegate_(delegate) {}
|
||||
|
||||
AtomMenuModel::~AtomMenuModel() {}
|
||||
|
||||
void AtomMenuModel::SetRole(int index, const base::string16& role) {
|
||||
int command_id = GetCommandIdAt(index);
|
||||
roles_[command_id] = role;
|
||||
}
|
||||
|
||||
base::string16 AtomMenuModel::GetRoleAt(int index) {
|
||||
int command_id = GetCommandIdAt(index);
|
||||
if (base::ContainsKey(roles_, command_id))
|
||||
return roles_[command_id];
|
||||
else
|
||||
return base::string16();
|
||||
}
|
||||
|
||||
bool AtomMenuModel::GetAcceleratorAtWithParams(
|
||||
int index,
|
||||
bool use_default_accelerator,
|
||||
ui::Accelerator* accelerator) const {
|
||||
if (delegate_) {
|
||||
return delegate_->GetAcceleratorForCommandIdWithParams(
|
||||
GetCommandIdAt(index), use_default_accelerator, accelerator);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtomMenuModel::ShouldRegisterAcceleratorAt(int index) const {
|
||||
if (delegate_) {
|
||||
return delegate_->ShouldRegisterAcceleratorForCommandId(
|
||||
GetCommandIdAt(index));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtomMenuModel::WorksWhenHiddenAt(int index) const {
|
||||
if (delegate_) {
|
||||
return delegate_->ShouldCommandIdWorkWhenHidden(GetCommandIdAt(index));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AtomMenuModel::MenuWillClose() {
|
||||
ui::SimpleMenuModel::MenuWillClose();
|
||||
for (Observer& observer : observers_) {
|
||||
observer.OnMenuWillClose();
|
||||
}
|
||||
}
|
||||
|
||||
void AtomMenuModel::MenuWillShow() {
|
||||
ui::SimpleMenuModel::MenuWillShow();
|
||||
for (Observer& observer : observers_) {
|
||||
observer.OnMenuWillShow();
|
||||
}
|
||||
}
|
||||
|
||||
AtomMenuModel* AtomMenuModel::GetSubmenuModelAt(int index) {
|
||||
return static_cast<AtomMenuModel*>(
|
||||
ui::SimpleMenuModel::GetSubmenuModelAt(index));
|
||||
}
|
||||
|
||||
} // namespace atom
|
82
shell/browser/ui/atom_menu_model.h
Normal file
82
shell/browser/ui/atom_menu_model.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2015 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
|
||||
#define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "base/observer_list.h"
|
||||
#include "base/observer_list_types.h"
|
||||
#include "ui/base/models/simple_menu_model.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class AtomMenuModel : public ui::SimpleMenuModel {
|
||||
public:
|
||||
class Delegate : public ui::SimpleMenuModel::Delegate {
|
||||
public:
|
||||
~Delegate() override {}
|
||||
|
||||
virtual bool GetAcceleratorForCommandIdWithParams(
|
||||
int command_id,
|
||||
bool use_default_accelerator,
|
||||
ui::Accelerator* accelerator) const = 0;
|
||||
|
||||
virtual bool ShouldRegisterAcceleratorForCommandId(
|
||||
int command_id) const = 0;
|
||||
|
||||
virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0;
|
||||
|
||||
private:
|
||||
// ui::SimpleMenuModel::Delegate:
|
||||
bool GetAcceleratorForCommandId(
|
||||
int command_id,
|
||||
ui::Accelerator* accelerator) const override;
|
||||
};
|
||||
|
||||
class Observer : public base::CheckedObserver {
|
||||
public:
|
||||
~Observer() override {}
|
||||
|
||||
// Notifies the menu will open.
|
||||
virtual void OnMenuWillShow() {}
|
||||
|
||||
// Notifies the menu has been closed.
|
||||
virtual void OnMenuWillClose() {}
|
||||
};
|
||||
|
||||
explicit AtomMenuModel(Delegate* delegate);
|
||||
~AtomMenuModel() override;
|
||||
|
||||
void AddObserver(Observer* obs) { observers_.AddObserver(obs); }
|
||||
void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); }
|
||||
|
||||
void SetRole(int index, const base::string16& role);
|
||||
base::string16 GetRoleAt(int index);
|
||||
bool GetAcceleratorAtWithParams(int index,
|
||||
bool use_default_accelerator,
|
||||
ui::Accelerator* accelerator) const;
|
||||
bool ShouldRegisterAcceleratorAt(int index) const;
|
||||
bool WorksWhenHiddenAt(int index) const;
|
||||
|
||||
// ui::SimpleMenuModel:
|
||||
void MenuWillClose() override;
|
||||
void MenuWillShow() override;
|
||||
|
||||
using SimpleMenuModel::GetSubmenuModelAt;
|
||||
AtomMenuModel* GetSubmenuModelAt(int index);
|
||||
|
||||
private:
|
||||
Delegate* delegate_; // weak ref.
|
||||
|
||||
std::map<int, base::string16> roles_; // command id -> role
|
||||
base::ObserverList<Observer> observers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomMenuModel);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
|
219
shell/browser/ui/autofill_popup.cc
Normal file
219
shell/browser/ui/autofill_popup.cc
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Copyright (c) 2012 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 <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/ui/autofill_popup.h"
|
||||
#include "atom/common/api/api.mojom.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "chrome/browser/ui/autofill/popup_view_common.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
#include "ui/display/display.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/geometry/rect_conversions.h"
|
||||
#include "ui/gfx/geometry/vector2d.h"
|
||||
#include "ui/gfx/text_utils.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
#include "atom/browser/osr/osr_render_widget_host_view.h"
|
||||
#include "atom/browser/osr/osr_view_proxy.h"
|
||||
#endif
|
||||
|
||||
namespace atom {
|
||||
|
||||
class PopupViewCommon : public autofill::PopupViewCommon {
|
||||
public:
|
||||
explicit PopupViewCommon(const gfx::Rect& window_bounds)
|
||||
: window_bounds_(window_bounds) {}
|
||||
|
||||
gfx::Rect GetWindowBounds(gfx::NativeView container_view) override {
|
||||
return window_bounds_;
|
||||
}
|
||||
|
||||
private:
|
||||
gfx::Rect window_bounds_;
|
||||
};
|
||||
|
||||
AutofillPopup::AutofillPopup() {
|
||||
bold_font_list_ = gfx::FontList().DeriveWithWeight(gfx::Font::Weight::BOLD);
|
||||
smaller_font_list_ =
|
||||
gfx::FontList().DeriveWithSizeDelta(kSmallerFontSizeDelta);
|
||||
}
|
||||
|
||||
AutofillPopup::~AutofillPopup() {
|
||||
Hide();
|
||||
}
|
||||
|
||||
void AutofillPopup::CreateView(content::RenderFrameHost* frame_host,
|
||||
content::RenderFrameHost* embedder_frame_host,
|
||||
bool offscreen,
|
||||
views::View* parent,
|
||||
const gfx::RectF& r) {
|
||||
Hide();
|
||||
|
||||
frame_host_ = frame_host;
|
||||
element_bounds_ = gfx::ToEnclosedRect(r);
|
||||
|
||||
gfx::Vector2d height_offset(0, element_bounds_.height());
|
||||
gfx::Point menu_position(element_bounds_.origin() + height_offset);
|
||||
views::View::ConvertPointToScreen(parent, &menu_position);
|
||||
popup_bounds_ = gfx::Rect(menu_position, element_bounds_.size());
|
||||
|
||||
parent_ = parent;
|
||||
parent_->AddObserver(this);
|
||||
|
||||
view_ = new AutofillPopupView(this, parent->GetWidget());
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
if (offscreen) {
|
||||
auto* rwhv = frame_host->GetView();
|
||||
if (embedder_frame_host != nullptr) {
|
||||
rwhv = embedder_frame_host->GetView();
|
||||
}
|
||||
|
||||
auto* osr_rwhv = static_cast<OffScreenRenderWidgetHostView*>(rwhv);
|
||||
view_->view_proxy_.reset(new OffscreenViewProxy(view_));
|
||||
osr_rwhv->AddViewProxy(view_->view_proxy_.get());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Do this after OSR setup, we check for view_proxy_ when showing
|
||||
view_->Show();
|
||||
}
|
||||
|
||||
void AutofillPopup::Hide() {
|
||||
if (parent_) {
|
||||
parent_->RemoveObserver(this);
|
||||
parent_ = nullptr;
|
||||
}
|
||||
if (view_) {
|
||||
view_->Hide();
|
||||
view_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopup::SetItems(const std::vector<base::string16>& values,
|
||||
const std::vector<base::string16>& labels) {
|
||||
DCHECK(view_);
|
||||
values_ = values;
|
||||
labels_ = labels;
|
||||
UpdatePopupBounds();
|
||||
view_->OnSuggestionsChanged();
|
||||
if (view_) // could be hidden after the change
|
||||
view_->DoUpdateBoundsAndRedrawPopup();
|
||||
}
|
||||
|
||||
void AutofillPopup::AcceptSuggestion(int index) {
|
||||
mojom::ElectronAutofillAgentAssociatedPtr autofill_agent;
|
||||
frame_host_->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
mojo::MakeRequest(&autofill_agent));
|
||||
autofill_agent->AcceptDataListSuggestion(GetValueAt(index));
|
||||
}
|
||||
|
||||
void AutofillPopup::UpdatePopupBounds() {
|
||||
DCHECK(parent_);
|
||||
gfx::Point origin(element_bounds_.origin());
|
||||
views::View::ConvertPointToScreen(parent_, &origin);
|
||||
|
||||
gfx::Rect bounds(origin, element_bounds_.size());
|
||||
gfx::Rect window_bounds = parent_->GetBoundsInScreen();
|
||||
|
||||
PopupViewCommon popup_view_common(window_bounds);
|
||||
popup_bounds_ = popup_view_common.CalculatePopupBounds(
|
||||
GetDesiredPopupWidth(), GetDesiredPopupHeight(), bounds,
|
||||
gfx::NativeView(), base::i18n::IsRTL());
|
||||
}
|
||||
|
||||
gfx::Rect AutofillPopup::popup_bounds_in_view() {
|
||||
gfx::Point origin(popup_bounds_.origin());
|
||||
views::View::ConvertPointFromScreen(parent_, &origin);
|
||||
|
||||
return gfx::Rect(origin, popup_bounds_.size());
|
||||
}
|
||||
|
||||
void AutofillPopup::OnViewBoundsChanged(views::View* view) {
|
||||
UpdatePopupBounds();
|
||||
view_->DoUpdateBoundsAndRedrawPopup();
|
||||
}
|
||||
|
||||
void AutofillPopup::OnViewIsDeleting(views::View* view) {
|
||||
Hide();
|
||||
}
|
||||
|
||||
int AutofillPopup::GetDesiredPopupHeight() {
|
||||
return 2 * kPopupBorderThickness + values_.size() * kRowHeight;
|
||||
}
|
||||
|
||||
int AutofillPopup::GetDesiredPopupWidth() {
|
||||
int popup_width = element_bounds_.width();
|
||||
|
||||
for (size_t i = 0; i < values_.size(); ++i) {
|
||||
int row_size =
|
||||
kEndPadding + 2 * kPopupBorderThickness +
|
||||
gfx::GetStringWidth(GetValueAt(i), GetValueFontListForRow(i)) +
|
||||
gfx::GetStringWidth(GetLabelAt(i), GetLabelFontListForRow(i));
|
||||
if (GetLabelAt(i).length() > 0)
|
||||
row_size += kNamePadding + kEndPadding;
|
||||
|
||||
popup_width = std::max(popup_width, row_size);
|
||||
}
|
||||
|
||||
return popup_width;
|
||||
}
|
||||
|
||||
gfx::Rect AutofillPopup::GetRowBounds(int index) {
|
||||
int top = kPopupBorderThickness + index * kRowHeight;
|
||||
|
||||
return gfx::Rect(kPopupBorderThickness, top,
|
||||
popup_bounds_.width() - 2 * kPopupBorderThickness,
|
||||
kRowHeight);
|
||||
}
|
||||
|
||||
const gfx::FontList& AutofillPopup::GetValueFontListForRow(int index) const {
|
||||
return bold_font_list_;
|
||||
}
|
||||
|
||||
const gfx::FontList& AutofillPopup::GetLabelFontListForRow(int index) const {
|
||||
return smaller_font_list_;
|
||||
}
|
||||
|
||||
ui::NativeTheme::ColorId AutofillPopup::GetBackgroundColorIDForRow(
|
||||
int index) const {
|
||||
return (view_ && index == view_->GetSelectedLine())
|
||||
? ui::NativeTheme::kColorId_ResultsTableHoveredBackground
|
||||
: ui::NativeTheme::kColorId_ResultsTableNormalBackground;
|
||||
}
|
||||
|
||||
int AutofillPopup::GetLineCount() {
|
||||
return values_.size();
|
||||
}
|
||||
|
||||
base::string16 AutofillPopup::GetValueAt(int i) {
|
||||
return values_.at(i);
|
||||
}
|
||||
|
||||
base::string16 AutofillPopup::GetLabelAt(int i) {
|
||||
return labels_.at(i);
|
||||
}
|
||||
|
||||
int AutofillPopup::LineFromY(int y) const {
|
||||
int current_height = kPopupBorderThickness;
|
||||
|
||||
for (size_t i = 0; i < values_.size(); ++i) {
|
||||
current_height += kRowHeight;
|
||||
|
||||
if (y <= current_height)
|
||||
return i;
|
||||
}
|
||||
|
||||
return values_.size() - 1;
|
||||
}
|
||||
|
||||
} // namespace atom
|
91
shell/browser/ui/autofill_popup.h
Normal file
91
shell/browser/ui/autofill_popup.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright (c) 2012 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.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_AUTOFILL_POPUP_H_
|
||||
#define ATOM_BROWSER_UI_AUTOFILL_POPUP_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/ui/views/autofill_popup_view.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "ui/gfx/font_list.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "ui/views/view.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class AutofillPopupView;
|
||||
|
||||
class AutofillPopup : public views::ViewObserver {
|
||||
public:
|
||||
AutofillPopup();
|
||||
~AutofillPopup() override;
|
||||
|
||||
void CreateView(content::RenderFrameHost* render_frame,
|
||||
content::RenderFrameHost* embedder_frame,
|
||||
bool offscreen,
|
||||
views::View* parent,
|
||||
const gfx::RectF& bounds);
|
||||
void Hide();
|
||||
|
||||
void SetItems(const std::vector<base::string16>& values,
|
||||
const std::vector<base::string16>& labels);
|
||||
void UpdatePopupBounds();
|
||||
|
||||
gfx::Rect popup_bounds_in_view();
|
||||
|
||||
private:
|
||||
friend class AutofillPopupView;
|
||||
|
||||
// views::ViewObserver:
|
||||
void OnViewBoundsChanged(views::View* view) override;
|
||||
void OnViewIsDeleting(views::View* view) override;
|
||||
|
||||
void AcceptSuggestion(int index);
|
||||
|
||||
int GetDesiredPopupHeight();
|
||||
int GetDesiredPopupWidth();
|
||||
gfx::Rect GetRowBounds(int i);
|
||||
const gfx::FontList& GetValueFontListForRow(int index) const;
|
||||
const gfx::FontList& GetLabelFontListForRow(int index) const;
|
||||
ui::NativeTheme::ColorId GetBackgroundColorIDForRow(int index) const;
|
||||
|
||||
int GetLineCount();
|
||||
base::string16 GetValueAt(int i);
|
||||
base::string16 GetLabelAt(int i);
|
||||
int LineFromY(int y) const;
|
||||
|
||||
int selected_index_;
|
||||
|
||||
// Popup location
|
||||
gfx::Rect popup_bounds_;
|
||||
|
||||
// Bounds of the autofilled element
|
||||
gfx::Rect element_bounds_;
|
||||
|
||||
// Datalist suggestions
|
||||
std::vector<base::string16> values_;
|
||||
std::vector<base::string16> labels_;
|
||||
|
||||
// Font lists for the suggestions
|
||||
gfx::FontList smaller_font_list_;
|
||||
gfx::FontList bold_font_list_;
|
||||
|
||||
// For sending the accepted suggestion to the render frame that
|
||||
// asked to open the popup
|
||||
content::RenderFrameHost* frame_host_ = nullptr;
|
||||
|
||||
// The popup view. The lifetime is managed by the owning Widget
|
||||
AutofillPopupView* view_ = nullptr;
|
||||
|
||||
// The parent view that the popup view shows on. Weak ref.
|
||||
views::View* parent_ = nullptr;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AutofillPopup);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_AUTOFILL_POPUP_H_
|
27
shell/browser/ui/certificate_trust.h
Normal file
27
shell/browser/ui/certificate_trust.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
||||
#define ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/cert/x509_certificate.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindow;
|
||||
} // namespace atom
|
||||
|
||||
namespace certificate_trust {
|
||||
|
||||
v8::Local<v8::Promise> ShowCertificateTrust(
|
||||
atom::NativeWindow* parent_window,
|
||||
const scoped_refptr<net::X509Certificate>& cert,
|
||||
const std::string& message);
|
||||
|
||||
} // namespace certificate_trust
|
||||
|
||||
#endif // ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
124
shell/browser/ui/certificate_trust_mac.mm
Normal file
124
shell/browser/ui/certificate_trust_mac.mm
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/certificate_trust.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <SecurityInterface/SFCertificateTrustPanel.h>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "net/cert/cert_database.h"
|
||||
#include "net/cert/x509_util_ios_and_mac.h"
|
||||
#include "net/cert/x509_util_mac.h"
|
||||
|
||||
@interface TrustDelegate : NSObject {
|
||||
@private
|
||||
std::unique_ptr<atom::util::Promise> promise_;
|
||||
SFCertificateTrustPanel* panel_;
|
||||
scoped_refptr<net::X509Certificate> cert_;
|
||||
SecTrustRef trust_;
|
||||
CFArrayRef cert_chain_;
|
||||
SecPolicyRef sec_policy_;
|
||||
}
|
||||
|
||||
- (id)initWithPromise:(atom::util::Promise)promise
|
||||
panel:(SFCertificateTrustPanel*)panel
|
||||
cert:(const scoped_refptr<net::X509Certificate>&)cert
|
||||
trust:(SecTrustRef)trust
|
||||
certChain:(CFArrayRef)certChain
|
||||
secPolicy:(SecPolicyRef)secPolicy;
|
||||
|
||||
- (void)panelDidEnd:(NSWindow*)sheet
|
||||
returnCode:(int)returnCode
|
||||
contextInfo:(void*)contextInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TrustDelegate
|
||||
|
||||
- (void)dealloc {
|
||||
[panel_ release];
|
||||
CFRelease(trust_);
|
||||
CFRelease(cert_chain_);
|
||||
CFRelease(sec_policy_);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithPromise:(atom::util::Promise)promise
|
||||
panel:(SFCertificateTrustPanel*)panel
|
||||
cert:(const scoped_refptr<net::X509Certificate>&)cert
|
||||
trust:(SecTrustRef)trust
|
||||
certChain:(CFArrayRef)certChain
|
||||
secPolicy:(SecPolicyRef)secPolicy {
|
||||
if ((self = [super init])) {
|
||||
promise_.reset(new atom::util::Promise(std::move(promise)));
|
||||
panel_ = panel;
|
||||
cert_ = cert;
|
||||
trust_ = trust;
|
||||
cert_chain_ = certChain;
|
||||
sec_policy_ = secPolicy;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)panelDidEnd:(NSWindow*)sheet
|
||||
returnCode:(int)returnCode
|
||||
contextInfo:(void*)contextInfo {
|
||||
auto* cert_db = net::CertDatabase::GetInstance();
|
||||
// This forces Chromium to reload the certificate since it might be trusted
|
||||
// now.
|
||||
cert_db->NotifyObserversCertDBChanged();
|
||||
|
||||
promise_->Resolve();
|
||||
[self autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace certificate_trust {
|
||||
|
||||
v8::Local<v8::Promise> ShowCertificateTrust(
|
||||
atom::NativeWindow* parent_window,
|
||||
const scoped_refptr<net::X509Certificate>& cert,
|
||||
const std::string& message) {
|
||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||
atom::util::Promise promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
auto* sec_policy = SecPolicyCreateBasicX509();
|
||||
auto cert_chain =
|
||||
net::x509_util::CreateSecCertificateArrayForX509Certificate(cert.get());
|
||||
SecTrustRef trust = nullptr;
|
||||
SecTrustCreateWithCertificates(cert_chain, sec_policy, &trust);
|
||||
|
||||
NSWindow* window = parent_window
|
||||
? parent_window->GetNativeWindow().GetNativeNSWindow()
|
||||
: nil;
|
||||
auto msg = base::SysUTF8ToNSString(message);
|
||||
|
||||
auto panel = [[SFCertificateTrustPanel alloc] init];
|
||||
auto delegate = [[TrustDelegate alloc] initWithPromise:std::move(promise)
|
||||
panel:panel
|
||||
cert:cert
|
||||
trust:trust
|
||||
certChain:cert_chain
|
||||
secPolicy:sec_policy];
|
||||
[panel beginSheetForWindow:window
|
||||
modalDelegate:delegate
|
||||
didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:)
|
||||
contextInfo:nil
|
||||
trust:trust
|
||||
message:msg];
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace certificate_trust
|
87
shell/browser/ui/certificate_trust_win.cc
Normal file
87
shell/browser/ui/certificate_trust_win.cc
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/certificate_trust.h"
|
||||
|
||||
#include <windows.h> // windows.h must be included first
|
||||
|
||||
#include <wincrypt.h>
|
||||
|
||||
#include "net/cert/cert_database.h"
|
||||
#include "net/cert/x509_util_win.h"
|
||||
|
||||
namespace certificate_trust {
|
||||
|
||||
// Add the provided certificate to the Trusted Root Certificate Authorities
|
||||
// store for the current user.
|
||||
//
|
||||
// This requires prompting the user to confirm they trust the certificate.
|
||||
BOOL AddToTrustedRootStore(const PCCERT_CONTEXT cert_context,
|
||||
const scoped_refptr<net::X509Certificate>& cert) {
|
||||
auto* root_cert_store = CertOpenStore(
|
||||
CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"Root");
|
||||
|
||||
if (root_cert_store == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto result = CertAddCertificateContextToStore(
|
||||
root_cert_store, cert_context, CERT_STORE_ADD_REPLACE_EXISTING, NULL);
|
||||
|
||||
if (result) {
|
||||
// force Chromium to reload it's database for this certificate
|
||||
auto* cert_db = net::CertDatabase::GetInstance();
|
||||
cert_db->NotifyObserversCertDBChanged();
|
||||
}
|
||||
|
||||
CertCloseStore(root_cert_store, CERT_CLOSE_STORE_FORCE_FLAG);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CERT_CHAIN_PARA GetCertificateChainParameters() {
|
||||
CERT_ENHKEY_USAGE enhkey_usage;
|
||||
enhkey_usage.cUsageIdentifier = 0;
|
||||
enhkey_usage.rgpszUsageIdentifier = NULL;
|
||||
|
||||
CERT_USAGE_MATCH cert_usage;
|
||||
// ensure the rules are applied to the entire chain
|
||||
cert_usage.dwType = USAGE_MATCH_TYPE_AND;
|
||||
cert_usage.Usage = enhkey_usage;
|
||||
|
||||
CERT_CHAIN_PARA params = {sizeof(CERT_CHAIN_PARA)};
|
||||
params.RequestedUsage = cert_usage;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> ShowCertificateTrust(
|
||||
atom::NativeWindow* parent_window,
|
||||
const scoped_refptr<net::X509Certificate>& cert,
|
||||
const std::string& message) {
|
||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||
atom::util::Promise promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
PCCERT_CHAIN_CONTEXT chain_context;
|
||||
auto cert_context = net::x509_util::CreateCertContextWithChain(cert.get());
|
||||
auto params = GetCertificateChainParameters();
|
||||
|
||||
if (CertGetCertificateChain(NULL, cert_context.get(), NULL, NULL, ¶ms,
|
||||
NULL, NULL, &chain_context)) {
|
||||
auto error_status = chain_context->TrustStatus.dwErrorStatus;
|
||||
if (error_status == CERT_TRUST_IS_SELF_SIGNED ||
|
||||
error_status == CERT_TRUST_IS_UNTRUSTED_ROOT) {
|
||||
// these are the only scenarios we're interested in supporting
|
||||
AddToTrustedRootStore(cert_context.get(), cert);
|
||||
}
|
||||
|
||||
CertFreeCertificateChain(chain_context);
|
||||
}
|
||||
|
||||
promise.Resolve();
|
||||
return handle;
|
||||
}
|
||||
|
||||
} // namespace certificate_trust
|
18
shell/browser/ui/cocoa/NSColor+Hex.h
Normal file
18
shell/browser/ui/cocoa/NSColor+Hex.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Created by Mathias Leppich on 03/02/14.
|
||||
// Copyright (c) 2014 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface NSColor (Hex)
|
||||
- (NSString*)hexadecimalValue;
|
||||
- (NSString*)RGBAValue;
|
||||
+ (NSColor*)colorWithHexColorString:(NSString*)hex;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_NSCOLOR_HEX_H_
|
77
shell/browser/ui/cocoa/NSColor+Hex.mm
Normal file
77
shell/browser/ui/cocoa/NSColor+Hex.mm
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Created by Mathias Leppich on 03/02/14.
|
||||
// Copyright (c) 2014 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
|
||||
|
||||
@implementation NSColor (Hex)
|
||||
|
||||
- (NSString*)RGBAValue {
|
||||
double redFloatValue, greenFloatValue, blueFloatValue, alphaFloatValue;
|
||||
|
||||
NSColor* convertedColor =
|
||||
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
||||
|
||||
if (convertedColor) {
|
||||
[convertedColor getRed:&redFloatValue
|
||||
green:&greenFloatValue
|
||||
blue:&blueFloatValue
|
||||
alpha:&alphaFloatValue];
|
||||
|
||||
int redIntValue = redFloatValue * 255.99999f;
|
||||
int greenIntValue = greenFloatValue * 255.99999f;
|
||||
int blueIntValue = blueFloatValue * 255.99999f;
|
||||
int alphaIntValue = alphaFloatValue * 255.99999f;
|
||||
|
||||
return
|
||||
[NSString stringWithFormat:@"%02x%02x%02x%02x", redIntValue,
|
||||
greenIntValue, blueIntValue, alphaIntValue];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString*)hexadecimalValue {
|
||||
double redFloatValue, greenFloatValue, blueFloatValue;
|
||||
|
||||
NSColor* convertedColor =
|
||||
[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
||||
|
||||
if (convertedColor) {
|
||||
[convertedColor getRed:&redFloatValue
|
||||
green:&greenFloatValue
|
||||
blue:&blueFloatValue
|
||||
alpha:NULL];
|
||||
|
||||
int redIntValue = redFloatValue * 255.99999f;
|
||||
int greenIntValue = greenFloatValue * 255.99999f;
|
||||
int blueIntValue = blueFloatValue * 255.99999f;
|
||||
|
||||
return [NSString stringWithFormat:@"#%02x%02x%02x", redIntValue,
|
||||
greenIntValue, blueIntValue];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSColor*)colorWithHexColorString:(NSString*)inColorString {
|
||||
unsigned colorCode = 0;
|
||||
|
||||
if (inColorString) {
|
||||
NSScanner* scanner = [NSScanner scannerWithString:inColorString];
|
||||
(void)[scanner scanHexInt:&colorCode]; // ignore error
|
||||
}
|
||||
|
||||
unsigned char redByte = (unsigned char)(colorCode >> 16);
|
||||
unsigned char greenByte = (unsigned char)(colorCode >> 8);
|
||||
unsigned char blueByte = (unsigned char)(colorCode); // masks off high bits
|
||||
|
||||
return [NSColor colorWithCalibratedRed:(CGFloat)redByte / 0xff
|
||||
green:(CGFloat)greenByte / 0xff
|
||||
blue:(CGFloat)blueByte / 0xff
|
||||
alpha:1.0];
|
||||
}
|
||||
|
||||
@end
|
17
shell/browser/ui/cocoa/NSString+ANSI.h
Normal file
17
shell/browser/ui/cocoa/NSString+ANSI.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Created by Kent Karlsson on 3/11/16.
|
||||
// Copyright (c) 2016 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (ANSI)
|
||||
- (BOOL)containsANSICodes;
|
||||
- (NSMutableAttributedString*)attributedStringParsingANSICodes;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_NSSTRING_ANSI_H_
|
166
shell/browser/ui/cocoa/NSString+ANSI.mm
Normal file
166
shell/browser/ui/cocoa/NSString+ANSI.mm
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Created by Kent Karlsson on 3/11/16.
|
||||
// Copyright (c) 2016 Bit Bar. All rights reserved.
|
||||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/NSColor+Hex.h"
|
||||
#include "atom/browser/ui/cocoa/NSString+ANSI.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
|
||||
@implementation NSMutableDictionary (ANSI)
|
||||
|
||||
- (NSMutableDictionary*)modifyAttributesForANSICodes:(NSString*)codes {
|
||||
BOOL bold = NO;
|
||||
NSFont* font = self[NSFontAttributeName];
|
||||
|
||||
NSArray* codeArray = [codes componentsSeparatedByString:@";"];
|
||||
|
||||
for (NSString* codeString in codeArray) {
|
||||
int code = codeString.intValue;
|
||||
switch (code) {
|
||||
case 0:
|
||||
[self removeAllObjects];
|
||||
// remove italic and bold from font here
|
||||
if (font)
|
||||
self[NSFontAttributeName] = font;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case 22:
|
||||
bold = (code == 1);
|
||||
break;
|
||||
|
||||
// case 3: italic
|
||||
// case 23: italic off
|
||||
// case 4: underlined
|
||||
// case 24: underlined off
|
||||
|
||||
case 30:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"7f7f7f" : @"000000"];
|
||||
break;
|
||||
case 31:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cd0000" : @"ff0000"];
|
||||
break;
|
||||
case 32:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"00cd00" : @"00ff00"];
|
||||
break;
|
||||
case 33:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cdcd00" : @"ffff00"];
|
||||
break;
|
||||
case 34:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"0000ee" : @"5c5cff"];
|
||||
break;
|
||||
case 35:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"cd00cd" : @"ff00ff"];
|
||||
break;
|
||||
case 36:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"00cdcd" : @"00ffff"];
|
||||
break;
|
||||
case 37:
|
||||
self[NSForegroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:bold ? @"e5e5e5" : @"ffffff"];
|
||||
break;
|
||||
|
||||
case 39:
|
||||
[self removeObjectForKey:NSForegroundColorAttributeName];
|
||||
break;
|
||||
|
||||
case 40:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"7f7f7f"];
|
||||
break;
|
||||
case 41:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cd0000"];
|
||||
break;
|
||||
case 42:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"00cd00"];
|
||||
break;
|
||||
case 43:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cdcd00"];
|
||||
break;
|
||||
case 44:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"0000ee"];
|
||||
break;
|
||||
case 45:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"cd00cd"];
|
||||
break;
|
||||
case 46:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"00cdcd"];
|
||||
break;
|
||||
case 47:
|
||||
self[NSBackgroundColorAttributeName] =
|
||||
[NSColor colorWithHexColorString:@"e5e5e5"];
|
||||
break;
|
||||
|
||||
case 49:
|
||||
[self removeObjectForKey:NSBackgroundColorAttributeName];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString (ANSI)
|
||||
|
||||
- (BOOL)containsANSICodes {
|
||||
return [self rangeOfString:@"\033["].location != NSNotFound;
|
||||
}
|
||||
|
||||
- (NSMutableAttributedString*)attributedStringParsingANSICodes {
|
||||
NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
base::scoped_nsobject<NSMutableDictionary> attributes(
|
||||
[[NSMutableDictionary alloc] init]);
|
||||
NSArray* parts = [self componentsSeparatedByString:@"\033["];
|
||||
[result appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:parts.firstObject
|
||||
attributes:nil] autorelease]];
|
||||
|
||||
for (NSString* part in
|
||||
[parts subarrayWithRange:NSMakeRange(1, parts.count - 1)]) {
|
||||
if (part.length == 0)
|
||||
continue;
|
||||
|
||||
NSArray* sequence = [part componentsSeparatedByString:@"m"];
|
||||
NSString* text = sequence.lastObject;
|
||||
|
||||
if (sequence.count < 2) {
|
||||
[result
|
||||
appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:attributes] autorelease]];
|
||||
} else if (sequence.count >= 2) {
|
||||
text = [[sequence subarrayWithRange:NSMakeRange(1, sequence.count - 1)]
|
||||
componentsJoinedByString:@"m"];
|
||||
[attributes modifyAttributesForANSICodes:sequence[0]];
|
||||
[result
|
||||
appendAttributedString:[[[NSAttributedString alloc]
|
||||
initWithString:text
|
||||
attributes:attributes] autorelease]];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
43
shell/browser/ui/cocoa/atom_bundle_mover.h
Normal file
43
shell/browser/ui/cocoa/atom_bundle_mover.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "native_mate/persistent_dictionary.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace cocoa {
|
||||
|
||||
class AtomBundleMover {
|
||||
public:
|
||||
static bool Move(mate::Arguments* args);
|
||||
static bool IsCurrentAppInApplicationsFolder();
|
||||
|
||||
private:
|
||||
static bool IsInApplicationsFolder(NSString* bundlePath);
|
||||
static NSString* ContainingDiskImageDevice(NSString* bundlePath);
|
||||
static void Relaunch(NSString* destinationPath);
|
||||
static NSString* ShellQuotedString(NSString* string);
|
||||
static bool CopyBundle(NSString* srcPath, NSString* dstPath);
|
||||
static bool AuthorizedInstall(NSString* srcPath,
|
||||
NSString* dstPath,
|
||||
bool* canceled);
|
||||
static bool IsApplicationAtPathRunning(NSString* bundlePath);
|
||||
static bool DeleteOrTrash(NSString* path);
|
||||
static bool Trash(NSString* path);
|
||||
};
|
||||
|
||||
} // namespace cocoa
|
||||
|
||||
} // namespace ui
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_BUNDLE_MOVER_H_
|
425
shell/browser/ui/cocoa/atom_bundle_mover.mm
Normal file
425
shell/browser/ui/cocoa/atom_bundle_mover.mm
Normal file
|
@ -0,0 +1,425 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_bundle_mover.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Security/Security.h>
|
||||
#import <dlfcn.h>
|
||||
#import <sys/mount.h>
|
||||
#import <sys/param.h>
|
||||
|
||||
#import "atom/browser/browser.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace cocoa {
|
||||
|
||||
bool AtomBundleMover::Move(mate::Arguments* args) {
|
||||
// Path of the current bundle
|
||||
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
|
||||
|
||||
// Skip if the application is already in the Applications folder
|
||||
if (IsInApplicationsFolder(bundlePath))
|
||||
return true;
|
||||
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
|
||||
NSString* diskImageDevice = ContainingDiskImageDevice(bundlePath);
|
||||
|
||||
NSString* applicationsDirectory = [[NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationDirectory, NSLocalDomainMask, true) lastObject]
|
||||
stringByResolvingSymlinksInPath];
|
||||
NSString* bundleName = [bundlePath lastPathComponent];
|
||||
NSString* destinationPath =
|
||||
[applicationsDirectory stringByAppendingPathComponent:bundleName];
|
||||
|
||||
// Check if we can write to the applications directory
|
||||
// and then make sure that if the app already exists we can overwrite it
|
||||
bool needAuthorization =
|
||||
![fileManager isWritableFileAtPath:applicationsDirectory] |
|
||||
([fileManager fileExistsAtPath:destinationPath] &&
|
||||
![fileManager isWritableFileAtPath:destinationPath]);
|
||||
|
||||
// Activate app -- work-around for focus issues related to "scary file from
|
||||
// internet" OS dialog.
|
||||
if (![NSApp isActive]) {
|
||||
[NSApp activateIgnoringOtherApps:true];
|
||||
}
|
||||
|
||||
// Move to applications folder
|
||||
if (needAuthorization) {
|
||||
bool authorizationCanceled;
|
||||
|
||||
if (!AuthorizedInstall(bundlePath, destinationPath,
|
||||
&authorizationCanceled)) {
|
||||
if (authorizationCanceled) {
|
||||
// User rejected the authorization request
|
||||
args->ThrowError("User rejected the authorization request");
|
||||
return false;
|
||||
} else {
|
||||
args->ThrowError(
|
||||
"Failed to copy to applications directory even with authorization");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If a copy already exists in the Applications folder, put it in the Trash
|
||||
if ([fileManager fileExistsAtPath:destinationPath]) {
|
||||
// But first, make sure that it's not running
|
||||
if (IsApplicationAtPathRunning(destinationPath)) {
|
||||
// Give the running app focus and terminate myself
|
||||
[[NSTask
|
||||
launchedTaskWithLaunchPath:@"/usr/bin/open"
|
||||
arguments:[NSArray
|
||||
arrayWithObject:destinationPath]]
|
||||
waitUntilExit];
|
||||
atom::Browser::Get()->Quit();
|
||||
return true;
|
||||
} else {
|
||||
if (!Trash([applicationsDirectory
|
||||
stringByAppendingPathComponent:bundleName])) {
|
||||
args->ThrowError("Failed to delete existing application");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CopyBundle(bundlePath, destinationPath)) {
|
||||
args->ThrowError(
|
||||
"Failed to copy current bundle to the applications folder");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Trash the original app. It's okay if this fails.
|
||||
// NOTE: This final delete does not work if the source bundle is in a network
|
||||
// mounted volume.
|
||||
// Calling rm or file manager's delete method doesn't work either. It's
|
||||
// unlikely to happen but it'd be great if someone could fix this.
|
||||
if (diskImageDevice == nil && !DeleteOrTrash(bundlePath)) {
|
||||
// Could not delete original but we just don't care
|
||||
}
|
||||
|
||||
// Relaunch.
|
||||
Relaunch(destinationPath);
|
||||
|
||||
// Launched from within a disk image? -- unmount (if no files are open after 5
|
||||
// seconds, otherwise leave it mounted).
|
||||
if (diskImageDevice) {
|
||||
NSString* script = [NSString
|
||||
stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &",
|
||||
ShellQuotedString(diskImageDevice)];
|
||||
[NSTask launchedTaskWithLaunchPath:@"/bin/sh"
|
||||
arguments:[NSArray arrayWithObjects:@"-c", script,
|
||||
nil]];
|
||||
}
|
||||
|
||||
atom::Browser::Get()->Quit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsCurrentAppInApplicationsFolder() {
|
||||
return IsInApplicationsFolder([[NSBundle mainBundle] bundlePath]);
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsInApplicationsFolder(NSString* bundlePath) {
|
||||
// Check all the normal Application directories
|
||||
NSArray* applicationDirs = NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationDirectory, NSAllDomainsMask, true);
|
||||
for (NSString* appDir in applicationDirs) {
|
||||
if ([bundlePath hasPrefix:appDir])
|
||||
return true;
|
||||
}
|
||||
|
||||
// Also, handle the case that the user has some other Application directory
|
||||
// (perhaps on a separate data partition).
|
||||
if ([[bundlePath pathComponents] containsObject:@"Applications"])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NSString* AtomBundleMover::ContainingDiskImageDevice(NSString* bundlePath) {
|
||||
NSString* containingPath = [bundlePath stringByDeletingLastPathComponent];
|
||||
|
||||
struct statfs fs;
|
||||
if (statfs([containingPath fileSystemRepresentation], &fs) ||
|
||||
(fs.f_flags & MNT_ROOTFS))
|
||||
return nil;
|
||||
|
||||
NSString* device = [[NSFileManager defaultManager]
|
||||
stringWithFileSystemRepresentation:fs.f_mntfromname
|
||||
length:strlen(fs.f_mntfromname)];
|
||||
|
||||
NSTask* hdiutil = [[[NSTask alloc] init] autorelease];
|
||||
[hdiutil setLaunchPath:@"/usr/bin/hdiutil"];
|
||||
[hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]];
|
||||
[hdiutil setStandardOutput:[NSPipe pipe]];
|
||||
[hdiutil launch];
|
||||
[hdiutil waitUntilExit];
|
||||
|
||||
NSData* data =
|
||||
[[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile];
|
||||
|
||||
NSDictionary* info =
|
||||
[NSPropertyListSerialization propertyListWithData:data
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
|
||||
if (![info isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
NSArray* images = (NSArray*)[info objectForKey:@"images"];
|
||||
if (![images isKindOfClass:[NSArray class]])
|
||||
return nil;
|
||||
|
||||
for (NSDictionary* image in images) {
|
||||
if (![image isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
id systemEntities = [image objectForKey:@"system-entities"];
|
||||
if (![systemEntities isKindOfClass:[NSArray class]])
|
||||
return nil;
|
||||
|
||||
for (NSDictionary* systemEntity in systemEntities) {
|
||||
if (![systemEntity isKindOfClass:[NSDictionary class]])
|
||||
return nil;
|
||||
|
||||
NSString* devEntry = [systemEntity objectForKey:@"dev-entry"];
|
||||
if (![devEntry isKindOfClass:[NSString class]])
|
||||
return nil;
|
||||
|
||||
if ([devEntry isEqualToString:device])
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::AuthorizedInstall(NSString* srcPath,
|
||||
NSString* dstPath,
|
||||
bool* canceled) {
|
||||
if (canceled)
|
||||
*canceled = false;
|
||||
|
||||
// Make sure that the destination path is an app bundle. We're essentially
|
||||
// running 'sudo rm -rf' so we really don't want to screw this up.
|
||||
if (![[dstPath pathExtension] isEqualToString:@"app"])
|
||||
return false;
|
||||
|
||||
// Do some more checks
|
||||
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet
|
||||
whitespaceCharacterSet]]
|
||||
length] == 0)
|
||||
return false;
|
||||
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet
|
||||
whitespaceCharacterSet]]
|
||||
length] == 0)
|
||||
return false;
|
||||
|
||||
int pid, status;
|
||||
AuthorizationRef myAuthorizationRef;
|
||||
|
||||
// Get the authorization
|
||||
OSStatus err =
|
||||
AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
|
||||
kAuthorizationFlagDefaults, &myAuthorizationRef);
|
||||
if (err != errAuthorizationSuccess)
|
||||
return false;
|
||||
|
||||
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights myRights = {1, &myItems};
|
||||
AuthorizationFlags myFlags = (AuthorizationFlags)(
|
||||
kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights |
|
||||
kAuthorizationFlagPreAuthorize);
|
||||
|
||||
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags,
|
||||
NULL);
|
||||
if (err != errAuthorizationSuccess) {
|
||||
if (err == errAuthorizationCanceled && canceled)
|
||||
*canceled = true;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(
|
||||
AuthorizationRef authorization, const char* pathToTool,
|
||||
AuthorizationFlags options, char* const* arguments,
|
||||
FILE** communicationsPipe) = NULL;
|
||||
if (!security_AuthorizationExecuteWithPrivileges) {
|
||||
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to
|
||||
// still use it since there's no good alternative (without requiring code
|
||||
// signing). We'll look up the function through dyld and fail if it is no
|
||||
// longer accessible. If Apple removes the function entirely this will fail
|
||||
// gracefully. If they keep the function and throw some sort of exception,
|
||||
// this won't fail gracefully, but that's a risk we'll have to take for now.
|
||||
security_AuthorizationExecuteWithPrivileges = (OSStatus(*)(
|
||||
AuthorizationRef, const char*, AuthorizationFlags, char* const*,
|
||||
FILE**))dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
|
||||
}
|
||||
if (!security_AuthorizationExecuteWithPrivileges)
|
||||
goto fail;
|
||||
|
||||
// Delete the destination
|
||||
{
|
||||
char rf[] = "-rf";
|
||||
char* args[] = {rf, (char*)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(
|
||||
myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess)
|
||||
goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status))
|
||||
goto fail; // We don't care about exit status as the destination most
|
||||
// likely does not exist
|
||||
}
|
||||
|
||||
// Copy
|
||||
{
|
||||
char pR[] = "-pR";
|
||||
char* args[] = {pR, (char*)[srcPath fileSystemRepresentation],
|
||||
(char*)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(
|
||||
myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess)
|
||||
goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return true;
|
||||
|
||||
fail:
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::CopyBundle(NSString* srcPath, NSString* dstPath) {
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
NSError* error = nil;
|
||||
|
||||
if ([fileManager copyItemAtPath:srcPath toPath:dstPath error:&error]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
NSString* AtomBundleMover::ShellQuotedString(NSString* string) {
|
||||
return [NSString
|
||||
stringWithFormat:@"'%@'",
|
||||
[string stringByReplacingOccurrencesOfString:@"'"
|
||||
withString:@"'\\''"]];
|
||||
}
|
||||
|
||||
void AtomBundleMover::Relaunch(NSString* destinationPath) {
|
||||
// The shell script waits until the original app process terminates.
|
||||
// This is done so that the relaunched app opens as the front-most app.
|
||||
int pid = [[NSProcessInfo processInfo] processIdentifier];
|
||||
|
||||
// Command run just before running open /final/path
|
||||
NSString* preOpenCmd = @"";
|
||||
|
||||
NSString* quotedDestinationPath = ShellQuotedString(destinationPath);
|
||||
|
||||
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
|
||||
// duplicate "scary file from the internet" dialog.
|
||||
preOpenCmd = [NSString
|
||||
stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@",
|
||||
quotedDestinationPath];
|
||||
|
||||
NSString* script =
|
||||
[NSString stringWithFormat:
|
||||
@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; "
|
||||
@"done; %@; /usr/bin/open %@) &",
|
||||
pid, preOpenCmd, quotedDestinationPath];
|
||||
|
||||
[NSTask
|
||||
launchedTaskWithLaunchPath:@"/bin/sh"
|
||||
arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
|
||||
}
|
||||
|
||||
bool AtomBundleMover::IsApplicationAtPathRunning(NSString* bundlePath) {
|
||||
bundlePath = [bundlePath stringByStandardizingPath];
|
||||
|
||||
for (NSRunningApplication* runningApplication in
|
||||
[[NSWorkspace sharedWorkspace] runningApplications]) {
|
||||
NSString* runningAppBundlePath =
|
||||
[[[runningApplication bundleURL] path] stringByStandardizingPath];
|
||||
if ([runningAppBundlePath isEqualToString:bundlePath]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::Trash(NSString* path) {
|
||||
bool result = false;
|
||||
|
||||
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
|
||||
result = [[NSFileManager defaultManager]
|
||||
trashItemAtURL:[NSURL fileURLWithPath:path]
|
||||
resultingItemURL:NULL
|
||||
error:NULL];
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = [[NSWorkspace sharedWorkspace]
|
||||
performFileOperation:NSWorkspaceRecycleOperation
|
||||
source:[path stringByDeletingLastPathComponent]
|
||||
destination:@""
|
||||
files:[NSArray arrayWithObject:[path lastPathComponent]]
|
||||
tag:NULL];
|
||||
}
|
||||
|
||||
// As a last resort try trashing with AppleScript.
|
||||
// This allows us to trash the app in macOS Sierra even when the app is
|
||||
// running inside an app translocation image.
|
||||
if (!result) {
|
||||
auto* code = R"str(
|
||||
set theFile to POSIX file "%@"
|
||||
tell application "Finder"
|
||||
move theFile to trash
|
||||
end tell
|
||||
)str";
|
||||
NSAppleScript* appleScript = [[[NSAppleScript alloc]
|
||||
initWithSource:[NSString stringWithFormat:@(code), path]] autorelease];
|
||||
NSDictionary* errorDict = nil;
|
||||
NSAppleEventDescriptor* scriptResult =
|
||||
[appleScript executeAndReturnError:&errorDict];
|
||||
result = (scriptResult != nil);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AtomBundleMover::DeleteOrTrash(NSString* path) {
|
||||
NSError* error;
|
||||
|
||||
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
|
||||
return true;
|
||||
} else {
|
||||
return Trash(path);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cocoa
|
||||
|
||||
} // namespace ui
|
||||
|
||||
} // namespace atom
|
47
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.h
Normal file
47
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "ui/base/cocoa/base_view.h"
|
||||
|
||||
namespace atom {
|
||||
class InspectableWebContentsViewMac;
|
||||
}
|
||||
|
||||
using atom::InspectableWebContentsViewMac;
|
||||
|
||||
@interface AtomInspectableWebContentsView : BaseView <NSWindowDelegate> {
|
||||
@private
|
||||
atom::InspectableWebContentsViewMac* inspectableWebContentsView_;
|
||||
|
||||
base::scoped_nsobject<NSView> fake_view_;
|
||||
base::scoped_nsobject<NSWindow> devtools_window_;
|
||||
BOOL devtools_visible_;
|
||||
BOOL devtools_docked_;
|
||||
BOOL devtools_is_first_responder_;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
}
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view;
|
||||
- (void)removeObservers;
|
||||
- (void)notifyDevToolsFocused;
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate;
|
||||
- (BOOL)isDevToolsVisible;
|
||||
- (BOOL)isDevToolsFocused;
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate;
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy;
|
||||
- (void)setTitle:(NSString*)title;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_BRY_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
269
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.mm
Normal file
269
shell/browser/ui/cocoa/atom_inspectable_web_contents_view.mm
Normal file
|
@ -0,0 +1,269 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_inspectable_web_contents_view.h"
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_impl.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
#include "content/public/browser/render_widget_host_view.h"
|
||||
#include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
|
||||
|
||||
@implementation AtomInspectableWebContentsView
|
||||
|
||||
- (instancetype)initWithInspectableWebContentsViewMac:
|
||||
(InspectableWebContentsViewMac*)view {
|
||||
self = [super init];
|
||||
if (!self)
|
||||
return nil;
|
||||
|
||||
inspectableWebContentsView_ = view;
|
||||
devtools_visible_ = NO;
|
||||
devtools_docked_ = NO;
|
||||
devtools_is_first_responder_ = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(viewDidBecomeFirstResponder:)
|
||||
name:kViewDidBecomeFirstResponder
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:self
|
||||
selector:@selector(parentWindowBecameMain:)
|
||||
name:NSWindowDidBecomeMainNotification
|
||||
object:nil];
|
||||
|
||||
if (inspectableWebContentsView_->inspectable_web_contents()->IsGuest()) {
|
||||
fake_view_.reset([[NSView alloc] init]);
|
||||
[fake_view_ setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:fake_view_];
|
||||
} else {
|
||||
auto* contents = inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetWebContents();
|
||||
auto* contentsView = contents->GetNativeView().GetNativeNSView();
|
||||
[contentsView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||||
[self addSubview:contentsView];
|
||||
}
|
||||
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=348490.
|
||||
[self setWantsLayer:YES];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)removeObservers {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (IBAction)showDevTools:(id)sender {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->ShowDevTools(true);
|
||||
}
|
||||
|
||||
- (void)notifyDevToolsFocused {
|
||||
if (inspectableWebContentsView_->GetDelegate())
|
||||
inspectableWebContentsView_->GetDelegate()->DevToolsFocused();
|
||||
}
|
||||
|
||||
- (void)setDevToolsVisible:(BOOL)visible activate:(BOOL)activate {
|
||||
if (visible == devtools_visible_)
|
||||
return;
|
||||
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto* devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
devtools_visible_ = visible;
|
||||
if (devtools_docked_) {
|
||||
if (visible) {
|
||||
// Place the devToolsView under contentsView, notice that we didn't set
|
||||
// sizes for them until the setContentsResizingStrategy message.
|
||||
[self addSubview:devToolsView positioned:NSWindowBelow relativeTo:nil];
|
||||
[self adjustSubviews];
|
||||
|
||||
// Focus on web view.
|
||||
devToolsWebContents->RestoreFocus();
|
||||
} else {
|
||||
gfx::ScopedCocoaDisableScreenUpdates disabler;
|
||||
[devToolsView removeFromSuperview];
|
||||
[self adjustSubviews];
|
||||
}
|
||||
} else {
|
||||
if (visible) {
|
||||
if (activate) {
|
||||
[devtools_window_ makeKeyAndOrderFront:nil];
|
||||
} else {
|
||||
[devtools_window_ orderBack:nil];
|
||||
}
|
||||
} else {
|
||||
[devtools_window_ setDelegate:nil];
|
||||
[devtools_window_ close];
|
||||
devtools_window_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsVisible {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
- (BOOL)isDevToolsFocused {
|
||||
if (devtools_docked_) {
|
||||
return [[self window] isKeyWindow] && devtools_is_first_responder_;
|
||||
} else {
|
||||
return [devtools_window_ isKeyWindow];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsDocked:(BOOL)docked activate:(BOOL)activate {
|
||||
// Revert to no-devtools state.
|
||||
[self setDevToolsVisible:NO activate:NO];
|
||||
|
||||
// Switch to new state.
|
||||
devtools_docked_ = docked;
|
||||
if (!docked) {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSMiniaturizableWindowMask | NSWindowStyleMaskResizable |
|
||||
NSTexturedBackgroundWindowMask |
|
||||
NSWindowStyleMaskUnifiedTitleAndToolbar;
|
||||
devtools_window_.reset([[EventDispatchingWindow alloc]
|
||||
initWithContentRect:NSMakeRect(0, 0, 800, 600)
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES]);
|
||||
[devtools_window_ setDelegate:self];
|
||||
[devtools_window_ setFrameAutosaveName:@"electron.devtools"];
|
||||
[devtools_window_ setTitle:@"Developer Tools"];
|
||||
[devtools_window_ setReleasedWhenClosed:NO];
|
||||
[devtools_window_ setAutorecalculatesContentBorderThickness:NO
|
||||
forEdge:NSMaxYEdge];
|
||||
[devtools_window_ setContentBorderThickness:24 forEdge:NSMaxYEdge];
|
||||
|
||||
NSView* contentView = [devtools_window_ contentView];
|
||||
devToolsView.frame = contentView.bounds;
|
||||
devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
|
||||
[contentView addSubview:devToolsView];
|
||||
}
|
||||
[self setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
- (void)setContentsResizingStrategy:
|
||||
(const DevToolsContentsResizingStrategy&)strategy {
|
||||
strategy_.CopyFrom(strategy);
|
||||
[self adjustSubviews];
|
||||
}
|
||||
|
||||
- (void)adjustSubviews {
|
||||
if (![[self subviews] count])
|
||||
return;
|
||||
|
||||
if (![self isDevToolsVisible] || devtools_window_) {
|
||||
DCHECK_EQ(1u, [[self subviews] count]);
|
||||
NSView* contents = [[self subviews] objectAtIndex:0];
|
||||
[contents setFrame:[self bounds]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSView* devToolsView = [[self subviews] objectAtIndex:0];
|
||||
NSView* contentsView = [[self subviews] objectAtIndex:1];
|
||||
|
||||
DCHECK_EQ(2u, [[self subviews] count]);
|
||||
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, gfx::Size(NSSizeToCGSize([self bounds].size)),
|
||||
&new_devtools_bounds, &new_contents_bounds);
|
||||
[devToolsView setFrame:[self flipRectToNSRect:new_devtools_bounds]];
|
||||
[contentsView setFrame:[self flipRectToNSRect:new_contents_bounds]];
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString*)title {
|
||||
[devtools_window_ setTitle:title];
|
||||
}
|
||||
|
||||
- (void)viewDidBecomeFirstResponder:(NSNotification*)notification {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
if (!inspectable_web_contents || inspectable_web_contents->IsGuest())
|
||||
return;
|
||||
auto* webContents = inspectable_web_contents->GetWebContents();
|
||||
auto* webContentsView = webContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
NSView* view = [notification object];
|
||||
if ([[webContentsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
if (!devToolsWebContents)
|
||||
return;
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
if ([[devToolsView subviews] containsObject:view]) {
|
||||
devtools_is_first_responder_ = YES;
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)parentWindowBecameMain:(NSNotification*)notification {
|
||||
NSWindow* parentWindow = [notification object];
|
||||
if ([self window] == parentWindow && devtools_docked_ &&
|
||||
devtools_is_first_responder_)
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
inspectableWebContentsView_->inspectable_web_contents()->CloseDevTools();
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->RestoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(true);
|
||||
|
||||
[self notifyDevToolsFocused];
|
||||
}
|
||||
|
||||
- (void)windowDidResignMain:(NSNotification*)notification {
|
||||
content::WebContents* web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents()
|
||||
->GetDevToolsWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
web_contents->StoreFocus();
|
||||
|
||||
content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
|
||||
if (rwhv)
|
||||
rwhv->SetActive(false);
|
||||
}
|
||||
|
||||
@end
|
62
shell/browser/ui/cocoa/atom_menu_controller.h
Normal file
62
shell/browser/ui/cocoa/atom_menu_controller.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "base/strings/string16.h"
|
||||
|
||||
namespace atom {
|
||||
class AtomMenuModel;
|
||||
}
|
||||
|
||||
// A controller for the cross-platform menu model. The menu that's created
|
||||
// has the tag and represented object set for each menu item. The object is a
|
||||
// NSValue holding a pointer to the model for that level of the menu (to
|
||||
// allow for hierarchical menus). The tag is the index into that model for
|
||||
// that particular item. It is important that the model outlives this object
|
||||
// as it only maintains weak references.
|
||||
@interface AtomMenuController : NSObject <NSMenuDelegate> {
|
||||
@protected
|
||||
atom::AtomMenuModel* model_; // weak
|
||||
base::scoped_nsobject<NSMenu> menu_;
|
||||
BOOL isMenuOpen_;
|
||||
BOOL useDefaultAccelerator_;
|
||||
base::Callback<void()> closeCallback;
|
||||
}
|
||||
|
||||
@property(nonatomic, assign) atom::AtomMenuModel* model;
|
||||
|
||||
// Builds a NSMenu from the pre-built model (must not be nil). Changes made
|
||||
// to the contents of the model after calling this will not be noticed.
|
||||
- (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use;
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
|
||||
|
||||
// Populate current NSMenu with |model|.
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model;
|
||||
|
||||
// Programmatically close the constructed menu.
|
||||
- (void)cancel;
|
||||
|
||||
// Access to the constructed menu if the complex initializer was used. If the
|
||||
// default initializer was used, then this will create the menu on first call.
|
||||
- (NSMenu*)menu;
|
||||
|
||||
// Whether the menu is currently open.
|
||||
- (BOOL)isMenuOpen;
|
||||
|
||||
// NSMenuDelegate methods this class implements. Subclasses should call super
|
||||
// if extending the behavior.
|
||||
- (void)menuWillOpen:(NSMenu*)menu;
|
||||
- (void)menuDidClose:(NSMenu*)menu;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
384
shell/browser/ui/cocoa/atom_menu_controller.mm
Normal file
384
shell/browser/ui/cocoa/atom_menu_controller.mm
Normal file
|
@ -0,0 +1,384 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_menu_controller.h"
|
||||
|
||||
#include "atom/browser/mac/atom_application.h"
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||
#include "ui/base/l10n/l10n_util_mac.h"
|
||||
#include "ui/events/cocoa/cocoa_event_utils.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Role {
|
||||
SEL selector;
|
||||
const char* role;
|
||||
};
|
||||
Role kRolesMap[] = {
|
||||
{@selector(orderFrontStandardAboutPanel:), "about"},
|
||||
{@selector(hide:), "hide"},
|
||||
{@selector(hideOtherApplications:), "hideothers"},
|
||||
{@selector(unhideAllApplications:), "unhide"},
|
||||
{@selector(arrangeInFront:), "front"},
|
||||
{@selector(undo:), "undo"},
|
||||
{@selector(redo:), "redo"},
|
||||
{@selector(cut:), "cut"},
|
||||
{@selector(copy:), "copy"},
|
||||
{@selector(paste:), "paste"},
|
||||
{@selector(delete:), "delete"},
|
||||
{@selector(pasteAndMatchStyle:), "pasteandmatchstyle"},
|
||||
{@selector(selectAll:), "selectall"},
|
||||
{@selector(startSpeaking:), "startspeaking"},
|
||||
{@selector(stopSpeaking:), "stopspeaking"},
|
||||
{@selector(performMiniaturize:), "minimize"},
|
||||
{@selector(performClose:), "close"},
|
||||
{@selector(performZoom:), "zoom"},
|
||||
{@selector(terminate:), "quit"},
|
||||
// ↓ is intentionally not `toggleFullScreen`. The macOS full screen menu
|
||||
// item behaves weird. If we use `toggleFullScreen`, then the menu item will
|
||||
// use the default label, and not take the one provided.
|
||||
{@selector(toggleFullScreenMode:), "togglefullscreen"},
|
||||
{@selector(toggleTabBar:), "toggletabbar"},
|
||||
{@selector(selectNextTab:), "selectnexttab"},
|
||||
{@selector(selectPreviousTab:), "selectprevioustab"},
|
||||
{@selector(mergeAllWindows:), "mergeallwindows"},
|
||||
{@selector(moveTabToNewWindow:), "movetabtonewwindow"},
|
||||
{@selector(clearRecentDocuments:), "clearrecentdocuments"},
|
||||
};
|
||||
|
||||
// Called when adding a submenu to the menu and checks if the submenu, via its
|
||||
// |model|, has visible child items.
|
||||
bool MenuHasVisibleItems(const atom::AtomMenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->IsVisibleAt(index))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called when an empty submenu is created. This inserts a menu item labeled
|
||||
// "(empty)" into the submenu. Matches Windows behavior.
|
||||
NSMenu* MakeEmptySubmenu() {
|
||||
base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] initWithTitle:@""]);
|
||||
NSString* empty_menu_title =
|
||||
l10n_util::GetNSString(IDS_APP_MENU_EMPTY_SUBMENU);
|
||||
|
||||
[submenu addItemWithTitle:empty_menu_title action:NULL keyEquivalent:@""];
|
||||
[[submenu itemAtIndex:0] setEnabled:NO];
|
||||
return submenu.autorelease();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Menu item is located for ease of removing it from the parent owner
|
||||
static base::scoped_nsobject<NSMenuItem> recentDocumentsMenuItem_;
|
||||
|
||||
// Submenu retained to be swapped back to |recentDocumentsMenuItem_|
|
||||
static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
|
||||
@implementation AtomMenuController
|
||||
|
||||
@synthesize model = model_;
|
||||
|
||||
- (id)initWithModel:(atom::AtomMenuModel*)model
|
||||
useDefaultAccelerator:(BOOL)use {
|
||||
if ((self = [super init])) {
|
||||
model_ = model;
|
||||
isMenuOpen_ = NO;
|
||||
useDefaultAccelerator_ = use;
|
||||
[self menu];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[menu_ setDelegate:nil];
|
||||
|
||||
// Close the menu if it is still open. This could happen if a tab gets closed
|
||||
// while its context menu is still open.
|
||||
[self cancel];
|
||||
|
||||
model_ = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback {
|
||||
closeCallback = callback;
|
||||
}
|
||||
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model {
|
||||
if (!menu_)
|
||||
return;
|
||||
|
||||
if (!recentDocumentsMenuItem_) {
|
||||
// Locate & retain the recent documents menu item
|
||||
recentDocumentsMenuItem_.reset(
|
||||
[[[[[NSApp mainMenu] itemWithTitle:@"Electron"] submenu]
|
||||
itemWithTitle:@"Open Recent"] retain]);
|
||||
}
|
||||
|
||||
model_ = model;
|
||||
[menu_ removeAllItems];
|
||||
|
||||
const int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
|
||||
[self addSeparatorToMenu:menu_ atIndex:index];
|
||||
else
|
||||
[self addItemToMenu:menu_ atIndex:index fromModel:model];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
if (isMenuOpen_) {
|
||||
[menu_ cancelTracking];
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a NSMenu from the given model. If the model has submenus, this can
|
||||
// be invoked recursively.
|
||||
- (NSMenu*)menuFromModel:(atom::AtomMenuModel*)model {
|
||||
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
|
||||
const int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->GetTypeAt(index) == atom::AtomMenuModel::TYPE_SEPARATOR)
|
||||
[self addSeparatorToMenu:menu atIndex:index];
|
||||
else
|
||||
[self addItemToMenu:menu atIndex:index fromModel:model];
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
// Adds a separator item at the given index. As the separator doesn't need
|
||||
// anything from the model, this method doesn't need the model index as the
|
||||
// other method below does.
|
||||
- (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index {
|
||||
NSMenuItem* separator = [NSMenuItem separatorItem];
|
||||
[menu insertItem:separator atIndex:index];
|
||||
}
|
||||
|
||||
// Empties the source menu items to the destination.
|
||||
- (void)moveMenuItems:(NSMenu*)source to:(NSMenu*)destination {
|
||||
const NSInteger count = [source numberOfItems];
|
||||
for (NSInteger index = 0; index < count; index++) {
|
||||
NSMenuItem* removedItem = [[[source itemAtIndex:0] retain] autorelease];
|
||||
[source removeItemAtIndex:0];
|
||||
[destination addItem:removedItem];
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces the item's submenu instance with the singleton recent documents
|
||||
// menu. Previously replaced menu items will be recovered.
|
||||
- (void)replaceSubmenuShowingRecentDocuments:(NSMenuItem*)item {
|
||||
NSMenu* recentDocumentsMenu =
|
||||
[[[recentDocumentsMenuItem_ submenu] retain] autorelease];
|
||||
|
||||
// Remove menu items in recent documents back to swap menu
|
||||
[self moveMenuItems:recentDocumentsMenu to:recentDocumentsMenuSwap_];
|
||||
// Swap back the submenu
|
||||
[recentDocumentsMenuItem_ setSubmenu:recentDocumentsMenuSwap_];
|
||||
|
||||
// Retain the item's submenu for a future recovery
|
||||
recentDocumentsMenuSwap_.reset([[item submenu] retain]);
|
||||
|
||||
// Repopulate with items from the submenu to be replaced
|
||||
[self moveMenuItems:recentDocumentsMenuSwap_ to:recentDocumentsMenu];
|
||||
// Update the submenu's title
|
||||
[recentDocumentsMenu setTitle:[recentDocumentsMenuSwap_ title]];
|
||||
// Replace submenu
|
||||
[item setSubmenu:recentDocumentsMenu];
|
||||
|
||||
// Remember the new menu item that carries the recent documents menu
|
||||
recentDocumentsMenuItem_.reset([item retain]);
|
||||
}
|
||||
|
||||
// Adds an item or a hierarchical menu to the item at the |index|,
|
||||
// associated with the entry in the model identified by |modelIndex|.
|
||||
- (void)addItemToMenu:(NSMenu*)menu
|
||||
atIndex:(NSInteger)index
|
||||
fromModel:(atom::AtomMenuModel*)model {
|
||||
base::string16 label16 = model->GetLabelAt(index);
|
||||
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
|
||||
|
||||
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
|
||||
initWithTitle:label
|
||||
action:@selector(itemSelected:)
|
||||
keyEquivalent:@""]);
|
||||
|
||||
// If the menu item has an icon, set it.
|
||||
gfx::Image icon;
|
||||
if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
|
||||
[item setImage:icon.ToNSImage()];
|
||||
|
||||
base::string16 role = model->GetRoleAt(index);
|
||||
atom::AtomMenuModel::ItemType type = model->GetTypeAt(index);
|
||||
|
||||
if (role == base::ASCIIToUTF16("services")) {
|
||||
base::string16 title = base::ASCIIToUTF16("Services");
|
||||
NSString* label = l10n_util::FixUpWindowsStyleLabel(title);
|
||||
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
NSMenu* submenu = [[NSMenu alloc] initWithTitle:label];
|
||||
[item setSubmenu:submenu];
|
||||
[NSApp setServicesMenu:submenu];
|
||||
} else if (type == atom::AtomMenuModel::TYPE_SUBMENU &&
|
||||
model->IsVisibleAt(index)) {
|
||||
// We need to specifically check that the submenu top-level item has been
|
||||
// enabled as it's not validated by validateUserInterfaceItem
|
||||
if (!model->IsEnabledAt(index))
|
||||
[item setEnabled:NO];
|
||||
|
||||
// Recursively build a submenu from the sub-model at this index.
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
atom::AtomMenuModel* submenuModel =
|
||||
static_cast<atom::AtomMenuModel*>(model->GetSubmenuModelAt(index));
|
||||
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
||||
? [self menuFromModel:submenuModel]
|
||||
: MakeEmptySubmenu();
|
||||
[submenu setTitle:[item title]];
|
||||
[item setSubmenu:submenu];
|
||||
|
||||
// Set submenu's role.
|
||||
if ((role == base::ASCIIToUTF16("window") ||
|
||||
role == base::ASCIIToUTF16("windowmenu")) &&
|
||||
[submenu numberOfItems])
|
||||
[NSApp setWindowsMenu:submenu];
|
||||
else if (role == base::ASCIIToUTF16("help"))
|
||||
[NSApp setHelpMenu:submenu];
|
||||
else if (role == base::ASCIIToUTF16("recentdocuments"))
|
||||
[self replaceSubmenuShowingRecentDocuments:item];
|
||||
} else {
|
||||
// The MenuModel works on indexes so we can't just set the command id as the
|
||||
// tag like we do in other menus. Also set the represented object to be
|
||||
// the model so hierarchical menus check the correct index in the correct
|
||||
// model. Setting the target to |self| allows this class to participate
|
||||
// in validation of the menu items.
|
||||
[item setTag:index];
|
||||
NSValue* modelObject = [NSValue valueWithPointer:model];
|
||||
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
|
||||
&accelerator)) {
|
||||
NSString* key_equivalent;
|
||||
NSUInteger modifier_mask;
|
||||
GetKeyEquivalentAndModifierMaskFromAccelerator(
|
||||
accelerator, &key_equivalent, &modifier_mask);
|
||||
[item setKeyEquivalent:key_equivalent];
|
||||
[item setKeyEquivalentModifierMask:modifier_mask];
|
||||
}
|
||||
|
||||
if (@available(macOS 10.13, *)) {
|
||||
[(id)item
|
||||
setAllowsKeyEquivalentWhenHidden:(model->WorksWhenHiddenAt(index))];
|
||||
}
|
||||
|
||||
// Set menu item's role.
|
||||
[item setTarget:self];
|
||||
if (!role.empty()) {
|
||||
for (const Role& pair : kRolesMap) {
|
||||
if (role == base::ASCIIToUTF16(pair.role)) {
|
||||
[item setTarget:nil];
|
||||
[item setAction:pair.selector];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[menu insertItem:item atIndex:index];
|
||||
}
|
||||
|
||||
// Called before the menu is to be displayed to update the state (enabled,
|
||||
// radio, etc) of each item in the menu.
|
||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
||||
SEL action = [item action];
|
||||
if (action != @selector(itemSelected:))
|
||||
return NO;
|
||||
|
||||
NSInteger modelIndex = [item tag];
|
||||
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
|
||||
[[(id)item representedObject] pointerValue]);
|
||||
DCHECK(model);
|
||||
if (model) {
|
||||
BOOL checked = model->IsItemCheckedAt(modelIndex);
|
||||
DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
|
||||
|
||||
[(id)item setState:(checked ? NSOnState : NSOffState)];
|
||||
[(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
|
||||
|
||||
return model->IsEnabledAt(modelIndex);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Called when the user chooses a particular menu item. |sender| is the menu
|
||||
// item chosen.
|
||||
- (void)itemSelected:(id)sender {
|
||||
NSInteger modelIndex = [sender tag];
|
||||
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(
|
||||
[[sender representedObject] pointerValue]);
|
||||
DCHECK(model);
|
||||
if (model) {
|
||||
NSEvent* event = [NSApp currentEvent];
|
||||
model->ActivatedAt(modelIndex,
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenu*)menu {
|
||||
if (menu_)
|
||||
return menu_.get();
|
||||
|
||||
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
|
||||
[menu_ setDelegate:self];
|
||||
if (model_)
|
||||
[self populateWithModel:model_];
|
||||
return menu_.get();
|
||||
}
|
||||
|
||||
- (BOOL)isMenuOpen {
|
||||
return isMenuOpen_;
|
||||
}
|
||||
|
||||
- (void)menuWillOpen:(NSMenu*)menu {
|
||||
isMenuOpen_ = YES;
|
||||
model_->MenuWillShow();
|
||||
}
|
||||
|
||||
- (void)menuDidClose:(NSMenu*)menu {
|
||||
if (isMenuOpen_) {
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
// Post async task so that itemSelected runs before the close callback
|
||||
// deletes the controller from the map which deallocates it
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
35
shell/browser/ui/cocoa/atom_native_widget_mac.h
Normal file
35
shell/browser/ui/cocoa/atom_native_widget_mac.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
||||
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowMac;
|
||||
|
||||
class AtomNativeWidgetMac : public views::NativeWidgetMac {
|
||||
public:
|
||||
AtomNativeWidgetMac(NativeWindowMac* shell,
|
||||
NSUInteger style_mask,
|
||||
views::internal::NativeWidgetDelegate* delegate);
|
||||
~AtomNativeWidgetMac() override;
|
||||
|
||||
protected:
|
||||
// NativeWidgetMac:
|
||||
NativeWidgetMacNSWindow* CreateNSWindow(
|
||||
const remote_cocoa::mojom::CreateWindowParams* params) override;
|
||||
|
||||
private:
|
||||
NativeWindowMac* shell_;
|
||||
NSUInteger style_mask_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomNativeWidgetMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NATIVE_WIDGET_MAC_H_
|
27
shell/browser/ui/cocoa/atom_native_widget_mac.mm
Normal file
27
shell/browser/ui/cocoa/atom_native_widget_mac.mm
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_native_widget_mac.h"
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
AtomNativeWidgetMac::AtomNativeWidgetMac(
|
||||
NativeWindowMac* shell,
|
||||
NSUInteger style_mask,
|
||||
views::internal::NativeWidgetDelegate* delegate)
|
||||
: views::NativeWidgetMac(delegate),
|
||||
shell_(shell),
|
||||
style_mask_(style_mask) {}
|
||||
|
||||
AtomNativeWidgetMac::~AtomNativeWidgetMac() {}
|
||||
|
||||
NativeWidgetMacNSWindow* AtomNativeWidgetMac::CreateNSWindow(
|
||||
const remote_cocoa::mojom::CreateWindowParams* params) {
|
||||
return [[[AtomNSWindow alloc] initWithShell:shell_
|
||||
styleMask:style_mask_] autorelease];
|
||||
}
|
||||
|
||||
} // namespace atom
|
47
shell/browser/ui/cocoa/atom_ns_window.h
Normal file
47
shell/browser/ui/cocoa/atom_ns_window.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
#include "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowMac;
|
||||
|
||||
// Prevents window from resizing during the scope.
|
||||
class ScopedDisableResize {
|
||||
public:
|
||||
ScopedDisableResize() { disable_resize_ = true; }
|
||||
~ScopedDisableResize() { disable_resize_ = false; }
|
||||
|
||||
static bool IsResizeDisabled() { return disable_resize_; }
|
||||
|
||||
private:
|
||||
static bool disable_resize_;
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@interface AtomNSWindow : NativeWidgetMacNSWindow {
|
||||
@private
|
||||
atom::NativeWindowMac* shell_;
|
||||
}
|
||||
@property BOOL acceptsFirstMouse;
|
||||
@property BOOL enableLargerThanScreen;
|
||||
@property BOOL disableAutoHideCursor;
|
||||
@property BOOL disableKeyOrMainWindow;
|
||||
@property(nonatomic, retain) NSView* vibrantView;
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask;
|
||||
- (atom::NativeWindowMac*)shell;
|
||||
- (id)accessibilityFocusedUIElement;
|
||||
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect;
|
||||
- (void)toggleFullScreenMode:(id)sender;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_H_
|
191
shell/browser/ui/cocoa/atom_ns_window.mm
Normal file
191
shell/browser/ui/cocoa/atom_ns_window.mm
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window.h"
|
||||
|
||||
#include "atom/browser/native_window_mac.h"
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
#include "atom/browser/ui/cocoa/root_view_mac.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "ui/base/cocoa/window_size_constants.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
bool ScopedDisableResize::disable_resize_ = false;
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@implementation AtomNSWindow
|
||||
|
||||
@synthesize acceptsFirstMouse;
|
||||
@synthesize enableLargerThanScreen;
|
||||
@synthesize disableAutoHideCursor;
|
||||
@synthesize disableKeyOrMainWindow;
|
||||
@synthesize vibrantView;
|
||||
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell
|
||||
styleMask:(NSUInteger)styleMask {
|
||||
if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater
|
||||
styleMask:styleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES])) {
|
||||
shell_ = shell;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (atom::NativeWindowMac*)shell {
|
||||
return shell_;
|
||||
}
|
||||
|
||||
- (id)accessibilityFocusedUIElement {
|
||||
views::Widget* widget = shell_->widget();
|
||||
id superFocus = [super accessibilityFocusedUIElement];
|
||||
if (!widget || shell_->IsFocused())
|
||||
return superFocus;
|
||||
return nil;
|
||||
}
|
||||
- (NSRect)originalContentRectForFrameRect:(NSRect)frameRect {
|
||||
return [super contentRectForFrameRect:frameRect];
|
||||
}
|
||||
|
||||
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2)) {
|
||||
if (shell_->touch_bar())
|
||||
return [shell_->touch_bar() makeTouchBar];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
// NSWindow overrides.
|
||||
|
||||
- (void)swipeWithEvent:(NSEvent*)event {
|
||||
if (event.deltaY == 1.0) {
|
||||
shell_->NotifyWindowSwipe("up");
|
||||
} else if (event.deltaX == -1.0) {
|
||||
shell_->NotifyWindowSwipe("right");
|
||||
} else if (event.deltaY == -1.0) {
|
||||
shell_->NotifyWindowSwipe("down");
|
||||
} else if (event.deltaX == 1.0) {
|
||||
shell_->NotifyWindowSwipe("left");
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)contentRectForFrameRect:(NSRect)frameRect {
|
||||
if (shell_->has_frame())
|
||||
return [super contentRectForFrameRect:frameRect];
|
||||
else
|
||||
return frameRect;
|
||||
}
|
||||
|
||||
- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen {
|
||||
// Resizing is disabled.
|
||||
if (atom::ScopedDisableResize::IsResizeDisabled())
|
||||
return [self frame];
|
||||
|
||||
// Enable the window to be larger than screen.
|
||||
if ([self enableLargerThanScreen])
|
||||
return frameRect;
|
||||
else
|
||||
return [super constrainFrameRect:frameRect toScreen:screen];
|
||||
}
|
||||
|
||||
- (void)setFrame:(NSRect)windowFrame display:(BOOL)displayViews {
|
||||
// constrainFrameRect is not called on hidden windows so disable adjusting
|
||||
// the frame directly when resize is disabled
|
||||
if (!atom::ScopedDisableResize::IsResizeDisabled())
|
||||
[super setFrame:windowFrame display:displayViews];
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute {
|
||||
if ([attribute isEqual:NSAccessibilityTitleAttribute])
|
||||
return base::SysUTF8ToNSString(shell_->GetTitle());
|
||||
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
||||
return [NSNumber numberWithBool:YES];
|
||||
if (![attribute isEqualToString:@"AXChildren"])
|
||||
return [super accessibilityAttributeValue:attribute];
|
||||
|
||||
// Filter out objects that aren't the title bar buttons. This has the effect
|
||||
// of removing the window title, which VoiceOver already sees.
|
||||
// * when VoiceOver is disabled, this causes Cmd+C to be used for TTS but
|
||||
// still leaves the buttons available in the accessibility tree.
|
||||
// * when VoiceOver is enabled, the full accessibility tree is used.
|
||||
// Without removing the title and with VO disabled, the TTS would always read
|
||||
// the window title instead of using Cmd+C to get the selected text.
|
||||
NSPredicate* predicate = [NSPredicate
|
||||
predicateWithFormat:@"(self isKindOfClass: %@) OR (self.className == %@)",
|
||||
[NSButtonCell class], @"RenderWidgetHostViewCocoa"];
|
||||
|
||||
NSArray* children = [super accessibilityAttributeValue:attribute];
|
||||
return [children filteredArrayUsingPredicate:predicate];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow {
|
||||
return !self.disableKeyOrMainWindow;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow {
|
||||
return !self.disableKeyOrMainWindow;
|
||||
}
|
||||
|
||||
- (NSView*)frameView {
|
||||
return [[self contentView] superview];
|
||||
}
|
||||
|
||||
// Quicklook methods
|
||||
|
||||
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)beginPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
panel.delegate = [self delegate];
|
||||
panel.dataSource = static_cast<id<QLPreviewPanelDataSource>>([self delegate]);
|
||||
}
|
||||
|
||||
- (void)endPreviewPanelControl:(QLPreviewPanel*)panel {
|
||||
panel.delegate = nil;
|
||||
panel.dataSource = nil;
|
||||
}
|
||||
|
||||
// Custom window button methods
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)performClose:(id)sender {
|
||||
if (shell_->title_bar_style() ==
|
||||
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER) {
|
||||
[[self delegate] windowShouldClose:self];
|
||||
} else if (shell_->IsSimpleFullScreen()) {
|
||||
if ([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if (![[self delegate] windowShouldClose:self])
|
||||
return;
|
||||
} else if ([self respondsToSelector:@selector(windowShouldClose:)]) {
|
||||
if (![self windowShouldClose:self])
|
||||
return;
|
||||
}
|
||||
[self close];
|
||||
} else {
|
||||
[super performClose:sender];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleFullScreenMode:(id)sender {
|
||||
if (shell_->simple_fullscreen())
|
||||
shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen());
|
||||
else
|
||||
[super toggleFullScreen:sender];
|
||||
}
|
||||
|
||||
- (void)performMiniaturize:(id)sender {
|
||||
if (shell_->title_bar_style() ==
|
||||
atom::NativeWindowMac::TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER)
|
||||
[self miniaturize:self];
|
||||
else
|
||||
[super performMiniaturize:sender];
|
||||
}
|
||||
|
||||
@end
|
27
shell/browser/ui/cocoa/atom_ns_window_delegate.h
Normal file
27
shell/browser/ui/cocoa/atom_ns_window_delegate.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
||||
|
||||
#include <Quartz/Quartz.h>
|
||||
|
||||
#include "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindowMac;
|
||||
}
|
||||
|
||||
@interface AtomNSWindowDelegate
|
||||
: ViewsNSWindowDelegate <NSTouchBarDelegate, QLPreviewPanelDataSource> {
|
||||
@private
|
||||
atom::NativeWindowMac* shell_;
|
||||
bool is_zooming_;
|
||||
int level_;
|
||||
bool is_resizable_;
|
||||
}
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_NS_WINDOW_DELEGATE_H_
|
306
shell/browser/ui/cocoa/atom_ns_window_delegate.mm
Normal file
306
shell/browser/ui/cocoa/atom_ns_window_delegate.mm
Normal file
|
@ -0,0 +1,306 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_ns_window_delegate.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/browser/native_window_mac.h"
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
#include "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
|
||||
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
using TitleBarStyle = atom::NativeWindowMac::TitleBarStyle;
|
||||
|
||||
@implementation AtomNSWindowDelegate
|
||||
|
||||
- (id)initWithShell:(atom::NativeWindowMac*)shell {
|
||||
// The views library assumes the window delegate must be an instance of
|
||||
// ViewsNSWindowDelegate, since we don't have a way to override the creation
|
||||
// of NSWindowDelegate, we have to dynamically replace the window delegate
|
||||
// on the fly.
|
||||
// TODO(zcbenz): Add interface in NativeWidgetMac to allow overriding creating
|
||||
// window delegate.
|
||||
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
|
||||
shell->GetNativeWindow());
|
||||
auto* bridged_view = bridge_host->bridge_impl();
|
||||
if ((self = [super initWithBridgedNativeWidget:bridged_view])) {
|
||||
shell_ = shell;
|
||||
is_zooming_ = false;
|
||||
level_ = [shell_->GetNativeWindow().GetNativeNSWindow() level];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - NSWindowDelegate
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification*)notification {
|
||||
// notification.object is the window that changed its state.
|
||||
// It's safe to use self.window instead if you don't assign one delegate to
|
||||
// many windows
|
||||
NSWindow* window = notification.object;
|
||||
|
||||
// check occlusion binary flag
|
||||
if (window.occlusionState & NSWindowOcclusionStateVisible) {
|
||||
// The app is visible
|
||||
shell_->NotifyWindowShow();
|
||||
} else {
|
||||
// The app is not visible
|
||||
shell_->NotifyWindowHide();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the user clicks the zoom button or selects it from the Window
|
||||
// menu to determine the "standard size" of the window.
|
||||
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
|
||||
defaultFrame:(NSRect)frame {
|
||||
if (!shell_->zoom_to_page_width())
|
||||
return frame;
|
||||
|
||||
// If the shift key is down, maximize.
|
||||
if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask)
|
||||
return frame;
|
||||
|
||||
// Get preferred width from observers. Usually the page width.
|
||||
int preferred_width = 0;
|
||||
shell_->NotifyWindowRequestPreferredWith(&preferred_width);
|
||||
|
||||
// Never shrink from the current size on zoom.
|
||||
NSRect window_frame = [window frame];
|
||||
CGFloat zoomed_width =
|
||||
std::max(static_cast<CGFloat>(preferred_width), NSWidth(window_frame));
|
||||
|
||||
// |frame| determines our maximum extents. We need to set the origin of the
|
||||
// frame -- and only move it left if necessary.
|
||||
if (window_frame.origin.x + zoomed_width > NSMaxX(frame))
|
||||
frame.origin.x = NSMaxX(frame) - zoomed_width;
|
||||
else
|
||||
frame.origin.x = window_frame.origin.x;
|
||||
|
||||
// Set the width. Don't touch y or height.
|
||||
frame.size.width = zoomed_width;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
||||
shell_->NotifyWindowFocus();
|
||||
}
|
||||
|
||||
- (void)windowDidResignMain:(NSNotification*)notification {
|
||||
shell_->NotifyWindowBlur();
|
||||
}
|
||||
|
||||
- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
|
||||
NSSize newSize = frameSize;
|
||||
double aspectRatio = shell_->GetAspectRatio();
|
||||
|
||||
if (aspectRatio > 0.0) {
|
||||
gfx::Size windowSize = shell_->GetSize();
|
||||
gfx::Size contentSize = shell_->GetContentSize();
|
||||
gfx::Size extraSize = shell_->GetAspectRatioExtraSize();
|
||||
|
||||
double extraWidthPlusFrame =
|
||||
windowSize.width() - contentSize.width() + extraSize.width();
|
||||
double extraHeightPlusFrame =
|
||||
windowSize.height() - contentSize.height() + extraSize.height();
|
||||
|
||||
newSize.width =
|
||||
roundf((frameSize.height - extraHeightPlusFrame) * aspectRatio +
|
||||
extraWidthPlusFrame);
|
||||
newSize.height =
|
||||
roundf((newSize.width - extraWidthPlusFrame) / aspectRatio +
|
||||
extraHeightPlusFrame);
|
||||
}
|
||||
|
||||
{
|
||||
bool prevent_default = false;
|
||||
gfx::Rect new_bounds(gfx::Point(sender.frame.origin), gfx::Size(newSize));
|
||||
shell_->NotifyWindowWillResize(new_bounds, &prevent_default);
|
||||
if (prevent_default) {
|
||||
return sender.frame.size;
|
||||
}
|
||||
}
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
- (void)windowDidResize:(NSNotification*)notification {
|
||||
[super windowDidResize:notification];
|
||||
shell_->NotifyWindowResize();
|
||||
}
|
||||
|
||||
- (void)windowDidMove:(NSNotification*)notification {
|
||||
[super windowDidMove:notification];
|
||||
// TODO(zcbenz): Remove the alias after figuring out a proper
|
||||
// way to dispatch move.
|
||||
shell_->NotifyWindowMove();
|
||||
shell_->NotifyWindowMoved();
|
||||
}
|
||||
|
||||
- (void)windowWillMiniaturize:(NSNotification*)notification {
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
// store the current status window level to be restored in
|
||||
// windowDidDeminiaturize
|
||||
level_ = [window level];
|
||||
[window setLevel:NSNormalWindowLevel];
|
||||
}
|
||||
|
||||
- (void)windowDidMiniaturize:(NSNotification*)notification {
|
||||
[super windowDidMiniaturize:notification];
|
||||
shell_->NotifyWindowMinimize();
|
||||
}
|
||||
|
||||
- (void)windowDidDeminiaturize:(NSNotification*)notification {
|
||||
[super windowDidDeminiaturize:notification];
|
||||
[shell_->GetNativeWindow().GetNativeNSWindow() setLevel:level_];
|
||||
shell_->NotifyWindowRestore();
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
|
||||
is_zooming_ = true;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)windowDidEndLiveResize:(NSNotification*)notification {
|
||||
if (is_zooming_) {
|
||||
if (shell_->IsMaximized())
|
||||
shell_->NotifyWindowMaximize();
|
||||
else
|
||||
shell_->NotifyWindowUnmaximize();
|
||||
is_zooming_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification*)notification {
|
||||
// Setting resizable to true before entering fullscreen
|
||||
is_resizable_ = shell_->IsResizable();
|
||||
shell_->SetResizable(true);
|
||||
// Hide the native toolbar before entering fullscreen, so there is no visual
|
||||
// artifacts.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
[window setToolbar:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidEnterFullScreen:(NSNotification*)notification {
|
||||
shell_->NotifyWindowEnterFullScreen();
|
||||
|
||||
// For frameless window we don't show set title for normal mode since the
|
||||
// titlebar is expected to be empty, but after entering fullscreen mode we
|
||||
// have to set one, because title bar is visible here.
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
if ((shell_->transparent() || !shell_->has_frame()) &&
|
||||
// FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under
|
||||
// fullscreen mode.
|
||||
// Show title if fullscreen_window_title flag is set
|
||||
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
|
||||
shell_->fullscreen_window_title())) {
|
||||
[window setTitleVisibility:NSWindowTitleVisible];
|
||||
}
|
||||
|
||||
// Restore the native toolbar immediately after entering fullscreen, if we
|
||||
// do this before leaving fullscreen, traffic light buttons will be jumping.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
base::scoped_nsobject<NSToolbar> toolbar(
|
||||
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
|
||||
[toolbar setShowsBaselineSeparator:NO];
|
||||
[window setToolbar:toolbar];
|
||||
|
||||
// Set window style to hide the toolbar, otherwise the toolbar will show
|
||||
// in fullscreen mode.
|
||||
[window setTitlebarAppearsTransparent:NO];
|
||||
shell_->SetStyleMask(true, NSWindowStyleMaskFullSizeContentView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowWillExitFullScreen:(NSNotification*)notification {
|
||||
// Restore the titlebar visibility.
|
||||
NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
|
||||
if ((shell_->transparent() || !shell_->has_frame()) &&
|
||||
(shell_->title_bar_style() != TitleBarStyle::HIDDEN_INSET ||
|
||||
shell_->fullscreen_window_title())) {
|
||||
[window setTitleVisibility:NSWindowTitleHidden];
|
||||
}
|
||||
|
||||
// Turn off the style for toolbar.
|
||||
if (shell_->title_bar_style() == TitleBarStyle::HIDDEN_INSET) {
|
||||
shell_->SetStyleMask(false, NSWindowStyleMaskFullSizeContentView);
|
||||
[window setTitlebarAppearsTransparent:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification*)notification {
|
||||
shell_->SetResizable(is_resizable_);
|
||||
shell_->NotifyWindowLeaveFullScreen();
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification*)notification {
|
||||
shell_->NotifyWindowClosed();
|
||||
|
||||
// Clears the delegate when window is going to be closed, since EL Capitan it
|
||||
// is possible that the methods of delegate would get called after the window
|
||||
// has been closed.
|
||||
auto* bridge_host = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
|
||||
shell_->GetNativeWindow());
|
||||
auto* bridged_view = bridge_host->bridge_impl();
|
||||
bridged_view->OnWindowWillClose();
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(id)window {
|
||||
shell_->NotifyWindowCloseButtonClicked();
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSRect)window:(NSWindow*)window
|
||||
willPositionSheet:(NSWindow*)sheet
|
||||
usingRect:(NSRect)rect {
|
||||
NSView* view = window.contentView;
|
||||
|
||||
rect.origin.x = shell_->GetSheetOffsetX();
|
||||
rect.origin.y = view.frame.size.height - shell_->GetSheetOffsetY();
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (void)windowWillBeginSheet:(NSNotification*)notification {
|
||||
shell_->NotifyWindowSheetBegin();
|
||||
}
|
||||
|
||||
- (void)windowDidEndSheet:(NSNotification*)notification {
|
||||
shell_->NotifyWindowSheetEnd();
|
||||
}
|
||||
|
||||
- (IBAction)newWindowForTab:(id)sender {
|
||||
shell_->NotifyNewWindowForTab();
|
||||
atom::Browser::Get()->NewWindowForTab();
|
||||
}
|
||||
|
||||
#pragma mark - NSTouchBarDelegate
|
||||
|
||||
- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
|
||||
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
if (touchBar && shell_->touch_bar())
|
||||
return [shell_->touch_bar() makeItemForIdentifier:identifier];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - QLPreviewPanelDataSource
|
||||
|
||||
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel*)panel {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (id<QLPreviewItem>)previewPanel:(QLPreviewPanel*)panel
|
||||
previewItemAtIndex:(NSInteger)index {
|
||||
return shell_->preview_item();
|
||||
}
|
||||
|
||||
@end
|
17
shell/browser/ui/cocoa/atom_preview_item.h
Normal file
17
shell/browser/ui/cocoa/atom_preview_item.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Quartz/Quartz.h>
|
||||
|
||||
@interface AtomPreviewItem : NSObject <QLPreviewItem>
|
||||
@property(nonatomic, retain) NSURL* previewItemURL;
|
||||
@property(nonatomic, retain) NSString* previewItemTitle;
|
||||
- (id)initWithURL:(NSURL*)url title:(NSString*)title;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_PREVIEW_ITEM_H_
|
21
shell/browser/ui/cocoa/atom_preview_item.mm
Normal file
21
shell/browser/ui/cocoa/atom_preview_item.mm
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/atom_preview_item.h"
|
||||
|
||||
@implementation AtomPreviewItem
|
||||
|
||||
@synthesize previewItemURL;
|
||||
@synthesize previewItemTitle;
|
||||
|
||||
- (id)initWithURL:(NSURL*)url title:(NSString*)title {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.previewItemURL = url;
|
||||
self.previewItemTitle = title;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
101
shell/browser/ui/cocoa/atom_touch_bar.h
Normal file
101
shell/browser/ui/cocoa/atom_touch_bar.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/cocoa/touch_bar_forward_declarations.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "native_mate/constructor.h"
|
||||
#include "native_mate/persistent_dictionary.h"
|
||||
|
||||
@interface AtomTouchBar : NSObject <NSScrubberDelegate,
|
||||
NSScrubberDataSource,
|
||||
NSScrubberFlowLayoutDelegate> {
|
||||
@protected
|
||||
std::vector<mate::PersistentDictionary> ordered_settings_;
|
||||
std::map<std::string, mate::PersistentDictionary> settings_;
|
||||
id<NSTouchBarDelegate> delegate_;
|
||||
atom::NativeWindow* window_;
|
||||
}
|
||||
|
||||
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
|
||||
window:(atom::NativeWindow*)window
|
||||
settings:(const std::vector<mate::PersistentDictionary>&)settings;
|
||||
|
||||
- (NSTouchBar*)makeTouchBar API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSMutableArray*)identifiersFromSettings:
|
||||
(const std::vector<mate::PersistentDictionary>&)settings;
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(const std::string&)item_id
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)addNonDefaultTouchBarItems:
|
||||
(const std::vector<mate::PersistentDictionary>&)items;
|
||||
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
|
||||
forTouchBar:(NSTouchBar*)touchBar
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
- (NSString*)idFromIdentifier:(NSString*)identifier
|
||||
withPrefix:(NSString*)prefix;
|
||||
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
|
||||
type:(const std::string&)typere;
|
||||
- (bool)hasItemWithID:(const std::string&)item_id;
|
||||
- (NSColor*)colorFromHexColorString:(const std::string&)colorString;
|
||||
|
||||
// Selector actions
|
||||
- (void)buttonAction:(id)sender;
|
||||
- (void)colorPickerAction:(id)sender API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)sliderAction:(id)sender API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
// Helpers to create touch bar items
|
||||
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
// Helpers to update touch bar items
|
||||
- (void)updateButton:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateLabel:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updateSlider:(NSSliderTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
- (void)updatePopover:(NSPopoverTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2));
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_TOUCH_BAR_H_
|
820
shell/browser/ui/cocoa/atom_touch_bar.mm
Normal file
820
shell/browser/ui/cocoa/atom_touch_bar.mm
Normal file
|
@ -0,0 +1,820 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_touch_bar.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/color_util.h"
|
||||
#include "atom/common/native_mate_converters/image_converter.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "skia/ext/skia_utils_mac.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
@implementation AtomTouchBar
|
||||
|
||||
static NSTouchBarItemIdentifier ButtonIdentifier =
|
||||
@"com.electron.touchbar.button.";
|
||||
static NSTouchBarItemIdentifier ColorPickerIdentifier =
|
||||
@"com.electron.touchbar.colorpicker.";
|
||||
static NSTouchBarItemIdentifier GroupIdentifier =
|
||||
@"com.electron.touchbar.group.";
|
||||
static NSTouchBarItemIdentifier LabelIdentifier =
|
||||
@"com.electron.touchbar.label.";
|
||||
static NSTouchBarItemIdentifier PopoverIdentifier =
|
||||
@"com.electron.touchbar.popover.";
|
||||
static NSTouchBarItemIdentifier SliderIdentifier =
|
||||
@"com.electron.touchbar.slider.";
|
||||
static NSTouchBarItemIdentifier SegmentedControlIdentifier =
|
||||
@"com.electron.touchbar.segmentedcontrol.";
|
||||
static NSTouchBarItemIdentifier ScrubberIdentifier =
|
||||
@"com.electron.touchbar.scrubber.";
|
||||
|
||||
static NSString* const TextScrubberItemIdentifier = @"scrubber.text.item";
|
||||
static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item";
|
||||
|
||||
- (id)initWithDelegate:(id<NSTouchBarDelegate>)delegate
|
||||
window:(atom::NativeWindow*)window
|
||||
settings:
|
||||
(const std::vector<mate::PersistentDictionary>&)settings {
|
||||
if ((self = [super init])) {
|
||||
delegate_ = delegate;
|
||||
window_ = window;
|
||||
ordered_settings_ = settings;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSTouchBar*)makeTouchBar {
|
||||
NSMutableArray* identifiers =
|
||||
[self identifiersFromSettings:ordered_settings_];
|
||||
return [self touchBarFromItemIdentifiers:identifiers];
|
||||
}
|
||||
|
||||
- (NSTouchBar*)touchBarFromItemIdentifiers:(NSMutableArray*)items {
|
||||
base::scoped_nsobject<NSTouchBar> bar(
|
||||
[[NSClassFromString(@"NSTouchBar") alloc] init]);
|
||||
[bar setDelegate:delegate_];
|
||||
[bar setDefaultItemIdentifiers:items];
|
||||
return bar.autorelease();
|
||||
}
|
||||
|
||||
- (NSMutableArray*)identifiersFromSettings:
|
||||
(const std::vector<mate::PersistentDictionary>&)dicts {
|
||||
NSMutableArray* identifiers = [NSMutableArray array];
|
||||
|
||||
if (@available(macOS 10.12.2, *)) {
|
||||
for (const auto& item : dicts) {
|
||||
std::string type;
|
||||
std::string item_id;
|
||||
if (item.Get("type", &type) && item.Get("id", &item_id)) {
|
||||
NSTouchBarItemIdentifier identifier = nil;
|
||||
if (type == "spacer") {
|
||||
std::string size;
|
||||
item.Get("size", &size);
|
||||
if (size == "large") {
|
||||
identifier = NSTouchBarItemIdentifierFixedSpaceLarge;
|
||||
} else if (size == "flexible") {
|
||||
identifier = NSTouchBarItemIdentifierFlexibleSpace;
|
||||
} else {
|
||||
identifier = NSTouchBarItemIdentifierFixedSpaceSmall;
|
||||
}
|
||||
} else {
|
||||
identifier = [self identifierFromID:item_id type:type];
|
||||
}
|
||||
|
||||
if (identifier) {
|
||||
settings_[item_id] = item;
|
||||
[identifiers addObject:identifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
[identifiers addObject:NSTouchBarItemIdentifierOtherItemsProxy];
|
||||
}
|
||||
|
||||
return identifiers;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
|
||||
NSString* item_id = nil;
|
||||
|
||||
if ([identifier hasPrefix:ButtonIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:ButtonIdentifier];
|
||||
return [self makeButtonForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:LabelIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:LabelIdentifier];
|
||||
return [self makeLabelForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:ColorPickerIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:ColorPickerIdentifier];
|
||||
return [self makeColorPickerForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:SliderIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:SliderIdentifier];
|
||||
return [self makeSliderForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:PopoverIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:PopoverIdentifier];
|
||||
return [self makePopoverForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:GroupIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:GroupIdentifier];
|
||||
return [self makeGroupForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:SegmentedControlIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:SegmentedControlIdentifier];
|
||||
return [self makeSegmentedControlForID:item_id withIdentifier:identifier];
|
||||
} else if ([identifier hasPrefix:ScrubberIdentifier]) {
|
||||
item_id = [self idFromIdentifier:identifier withPrefix:ScrubberIdentifier];
|
||||
return [self makeScrubberForID:item_id withIdentifier:identifier];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(NSTouchBarItemIdentifier)identifier
|
||||
withType:(const std::string&)item_type
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSTouchBarItem* item = [touchBar itemForIdentifier:identifier];
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (item_type == "button") {
|
||||
[self updateButton:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "label") {
|
||||
[self updateLabel:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "colorpicker") {
|
||||
[self updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:settings];
|
||||
} else if (item_type == "slider") {
|
||||
[self updateSlider:(NSSliderTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "popover") {
|
||||
[self updatePopover:(NSPopoverTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "segmented_control") {
|
||||
[self updateSegmentedControl:(NSCustomTouchBarItem*)item
|
||||
withSettings:settings];
|
||||
} else if (item_type == "scrubber") {
|
||||
[self updateScrubber:(NSCustomTouchBarItem*)item withSettings:settings];
|
||||
} else if (item_type == "group") {
|
||||
[self updateGroup:(NSGroupTouchBarItem*)item withSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addNonDefaultTouchBarItems:
|
||||
(const std::vector<mate::PersistentDictionary>&)items {
|
||||
[self identifiersFromSettings:items];
|
||||
}
|
||||
|
||||
- (void)setEscapeTouchBarItem:(const mate::PersistentDictionary&)item
|
||||
forTouchBar:(NSTouchBar*)touchBar {
|
||||
if (![touchBar
|
||||
respondsToSelector:@selector(escapeKeyReplacementItemIdentifier)])
|
||||
return;
|
||||
std::string type;
|
||||
std::string item_id;
|
||||
NSTouchBarItemIdentifier identifier = nil;
|
||||
if (item.Get("type", &type) && item.Get("id", &item_id)) {
|
||||
identifier = [self identifierFromID:item_id type:type];
|
||||
}
|
||||
if (identifier) {
|
||||
[self addNonDefaultTouchBarItems:{item}];
|
||||
touchBar.escapeKeyReplacementItemIdentifier = identifier;
|
||||
} else {
|
||||
touchBar.escapeKeyReplacementItemIdentifier = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshTouchBarItem:(NSTouchBar*)touchBar
|
||||
id:(const std::string&)item_id {
|
||||
if (![self hasItemWithID:item_id])
|
||||
return;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[item_id];
|
||||
std::string item_type;
|
||||
settings.Get("type", &item_type);
|
||||
|
||||
auto identifier = [self identifierFromID:item_id type:item_type];
|
||||
if (!identifier)
|
||||
return;
|
||||
|
||||
std::vector<mate::Dictionary> parents;
|
||||
settings.Get("_parents", &parents);
|
||||
for (auto& parent : parents) {
|
||||
std::string parent_type;
|
||||
std::string parent_id;
|
||||
if (!parent.Get("type", &parent_type) || !parent.Get("id", &parent_id))
|
||||
continue;
|
||||
auto parentIdentifier = [self identifierFromID:parent_id type:parent_type];
|
||||
if (!parentIdentifier)
|
||||
continue;
|
||||
|
||||
if (parent_type == "popover") {
|
||||
NSPopoverTouchBarItem* popoverItem =
|
||||
[touchBar itemForIdentifier:parentIdentifier];
|
||||
[self refreshTouchBarItem:popoverItem.popoverTouchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
} else if (parent_type == "group") {
|
||||
NSGroupTouchBarItem* groupItem =
|
||||
[touchBar itemForIdentifier:parentIdentifier];
|
||||
[self refreshTouchBarItem:groupItem.groupTouchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
}
|
||||
}
|
||||
|
||||
[self refreshTouchBarItem:touchBar
|
||||
id:identifier
|
||||
withType:item_type
|
||||
withSettings:settings];
|
||||
}
|
||||
|
||||
- (void)buttonAction:(id)sender {
|
||||
NSString* item_id =
|
||||
[NSString stringWithFormat:@"%ld", ((NSButton*)sender).tag];
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String],
|
||||
base::DictionaryValue());
|
||||
}
|
||||
|
||||
- (void)colorPickerAction:(id)sender {
|
||||
NSString* identifier = ((NSColorPickerTouchBarItem*)sender).identifier;
|
||||
NSString* item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:ColorPickerIdentifier];
|
||||
NSColor* color = ((NSColorPickerTouchBarItem*)sender).color;
|
||||
std::string hex_color = atom::ToRGBHex(skia::NSDeviceColorToSkColor(color));
|
||||
base::DictionaryValue details;
|
||||
details.SetString("color", hex_color);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (void)sliderAction:(id)sender {
|
||||
NSString* identifier = ((NSSliderTouchBarItem*)sender).identifier;
|
||||
NSString* item_id = [self idFromIdentifier:identifier
|
||||
withPrefix:SliderIdentifier];
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("value",
|
||||
[((NSSliderTouchBarItem*)sender).slider intValue]);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (NSString*)idFromIdentifier:(NSString*)identifier
|
||||
withPrefix:(NSString*)prefix {
|
||||
return [identifier substringFromIndex:[prefix length]];
|
||||
}
|
||||
|
||||
- (void)segmentedControlAction:(id)sender {
|
||||
NSString* item_id =
|
||||
[NSString stringWithFormat:@"%ld", ((NSSegmentedControl*)sender).tag];
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("selectedIndex",
|
||||
((NSSegmentedControl*)sender).selectedSegment);
|
||||
details.SetBoolean(
|
||||
"isSelected",
|
||||
[((NSSegmentedControl*)sender)
|
||||
isSelectedForSegment:((NSSegmentedControl*)sender).selectedSegment]);
|
||||
window_->NotifyTouchBarItemInteraction([item_id UTF8String], details);
|
||||
}
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didSelectItemAtIndex:(NSInteger)selectedIndex
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("selectedIndex", selectedIndex);
|
||||
details.SetString("type", "select");
|
||||
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
|
||||
details);
|
||||
}
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didHighlightItemAtIndex:(NSInteger)highlightedIndex
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
base::DictionaryValue details;
|
||||
details.SetInteger("highlightedIndex", highlightedIndex);
|
||||
details.SetString("type", "highlight");
|
||||
window_->NotifyTouchBarItemInteraction([scrubber.identifier UTF8String],
|
||||
details);
|
||||
}
|
||||
|
||||
- (NSTouchBarItemIdentifier)identifierFromID:(const std::string&)item_id
|
||||
type:(const std::string&)type {
|
||||
NSTouchBarItemIdentifier base_identifier = nil;
|
||||
if (type == "button")
|
||||
base_identifier = ButtonIdentifier;
|
||||
else if (type == "label")
|
||||
base_identifier = LabelIdentifier;
|
||||
else if (type == "colorpicker")
|
||||
base_identifier = ColorPickerIdentifier;
|
||||
else if (type == "slider")
|
||||
base_identifier = SliderIdentifier;
|
||||
else if (type == "popover")
|
||||
base_identifier = PopoverIdentifier;
|
||||
else if (type == "group")
|
||||
base_identifier = GroupIdentifier;
|
||||
else if (type == "segmented_control")
|
||||
base_identifier = SegmentedControlIdentifier;
|
||||
else if (type == "scrubber")
|
||||
base_identifier = ScrubberIdentifier;
|
||||
|
||||
if (base_identifier)
|
||||
return [NSString stringWithFormat:@"%@%s", base_identifier, item_id.data()];
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (bool)hasItemWithID:(const std::string&)item_id {
|
||||
return settings_.find(item_id) != settings_.end();
|
||||
}
|
||||
|
||||
- (NSColor*)colorFromHexColorString:(const std::string&)colorString {
|
||||
SkColor color = atom::ParseHexColor(colorString);
|
||||
return skia::SkColorToDeviceNSColor(color);
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeButtonForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
NSButton* button = [NSButton buttonWithTitle:@""
|
||||
target:self
|
||||
action:@selector(buttonAction:)];
|
||||
button.tag = [id floatValue];
|
||||
[item setView:button];
|
||||
[self updateButton:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateButton:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
NSButton* button = (NSButton*)item.view;
|
||||
|
||||
std::string backgroundColor;
|
||||
if (settings.Get("backgroundColor", &backgroundColor)) {
|
||||
button.bezelColor = [self colorFromHexColorString:backgroundColor];
|
||||
}
|
||||
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
button.title = base::SysUTF8ToNSString(label);
|
||||
|
||||
gfx::Image image;
|
||||
if (settings.Get("icon", &image)) {
|
||||
button.image = image.AsNSImage();
|
||||
|
||||
std::string iconPosition;
|
||||
settings.Get("iconPosition", &iconPosition);
|
||||
if (iconPosition == "left") {
|
||||
button.imagePosition = NSImageLeft;
|
||||
} else if (iconPosition == "right") {
|
||||
button.imagePosition = NSImageRight;
|
||||
} else {
|
||||
button.imagePosition = NSImageOverlaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeLabelForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setView:[NSTextField labelWithString:@""]];
|
||||
[self updateLabel:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateLabel:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
NSTextField* text_field = (NSTextField*)item.view;
|
||||
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
text_field.stringValue = base::SysUTF8ToNSString(label);
|
||||
|
||||
std::string textColor;
|
||||
if (settings.Get("textColor", &textColor) && !textColor.empty()) {
|
||||
text_field.textColor = [self colorFromHexColorString:textColor];
|
||||
} else {
|
||||
text_field.textColor = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeColorPickerForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSColorPickerTouchBarItem> item([[NSClassFromString(
|
||||
@"NSColorPickerTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setTarget:self];
|
||||
[item setAction:@selector(colorPickerAction:)];
|
||||
[self updateColorPicker:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateColorPicker:(NSColorPickerTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::vector<std::string> colors;
|
||||
if (settings.Get("availableColors", &colors) && !colors.empty()) {
|
||||
NSColorList* color_list =
|
||||
[[[NSColorList alloc] initWithName:@""] autorelease];
|
||||
for (size_t i = 0; i < colors.size(); ++i) {
|
||||
[color_list insertColor:[self colorFromHexColorString:colors[i]]
|
||||
key:base::SysUTF8ToNSString(colors[i])
|
||||
atIndex:i];
|
||||
}
|
||||
item.colorList = color_list;
|
||||
}
|
||||
|
||||
std::string selectedColor;
|
||||
if (settings.Get("selectedColor", &selectedColor)) {
|
||||
item.color = [self colorFromHexColorString:selectedColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeSliderForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSSliderTouchBarItem> item([[NSClassFromString(
|
||||
@"NSSliderTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[item setTarget:self];
|
||||
[item setAction:@selector(sliderAction:)];
|
||||
[self updateSlider:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateSlider:(NSSliderTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
item.label = base::SysUTF8ToNSString(label);
|
||||
|
||||
int maxValue = 100;
|
||||
int minValue = 0;
|
||||
int value = 50;
|
||||
settings.Get("minValue", &minValue);
|
||||
settings.Get("maxValue", &maxValue);
|
||||
settings.Get("value", &value);
|
||||
|
||||
item.slider.minValue = minValue;
|
||||
item.slider.maxValue = maxValue;
|
||||
item.slider.doubleValue = value;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makePopoverForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSPopoverTouchBarItem> item([[NSClassFromString(
|
||||
@"NSPopoverTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
[self updatePopover:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updatePopover:(NSPopoverTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings {
|
||||
std::string label;
|
||||
settings.Get("label", &label);
|
||||
item.collapsedRepresentationLabel = base::SysUTF8ToNSString(label);
|
||||
|
||||
gfx::Image image;
|
||||
if (settings.Get("icon", &image)) {
|
||||
item.collapsedRepresentationImage = image.AsNSImage();
|
||||
}
|
||||
|
||||
bool showCloseButton = true;
|
||||
settings.Get("showCloseButton", &showCloseButton);
|
||||
item.showsCloseButton = showCloseButton;
|
||||
|
||||
mate::PersistentDictionary child;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (settings.Get("child", &child) && child.Get("ordereredItems", &items)) {
|
||||
item.popoverTouchBar =
|
||||
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeGroupForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
|
||||
mate::PersistentDictionary child;
|
||||
if (!settings.Get("child", &child))
|
||||
return nil;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!child.Get("ordereredItems", &items))
|
||||
return nil;
|
||||
|
||||
NSMutableArray* generatedItems = [NSMutableArray array];
|
||||
NSMutableArray* identifiers = [self identifiersFromSettings:items];
|
||||
for (NSUInteger i = 0; i < [identifiers count]; ++i) {
|
||||
if ([identifiers objectAtIndex:i] !=
|
||||
NSTouchBarItemIdentifierOtherItemsProxy) {
|
||||
NSTouchBarItem* generatedItem =
|
||||
[self makeItemForIdentifier:[identifiers objectAtIndex:i]];
|
||||
if (generatedItem) {
|
||||
[generatedItems addObject:generatedItem];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [NSClassFromString(@"NSGroupTouchBarItem")
|
||||
groupItemWithIdentifier:identifier
|
||||
items:generatedItems];
|
||||
}
|
||||
|
||||
- (void)updateGroup:(NSGroupTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
mate::PersistentDictionary child;
|
||||
if (!settings.Get("child", &child))
|
||||
return;
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!child.Get("ordereredItems", &items))
|
||||
return;
|
||||
|
||||
item.groupTouchBar =
|
||||
[self touchBarFromItemIdentifiers:[self identifiersFromSettings:items]];
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeSegmentedControlForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
|
||||
NSSegmentedControl* control = [NSSegmentedControl
|
||||
segmentedControlWithLabels:[NSMutableArray array]
|
||||
trackingMode:NSSegmentSwitchTrackingSelectOne
|
||||
target:self
|
||||
action:@selector(segmentedControlAction:)];
|
||||
control.tag = [id floatValue];
|
||||
[item setView:control];
|
||||
|
||||
[self updateSegmentedControl:item withSettings:settings];
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateSegmentedControl:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSSegmentedControl* control = item.view;
|
||||
|
||||
std::string segmentStyle;
|
||||
settings.Get("segmentStyle", &segmentStyle);
|
||||
if (segmentStyle == "rounded")
|
||||
control.segmentStyle = NSSegmentStyleRounded;
|
||||
else if (segmentStyle == "textured-rounded")
|
||||
control.segmentStyle = NSSegmentStyleTexturedRounded;
|
||||
else if (segmentStyle == "round-rect")
|
||||
control.segmentStyle = NSSegmentStyleRoundRect;
|
||||
else if (segmentStyle == "textured-square")
|
||||
control.segmentStyle = NSSegmentStyleTexturedSquare;
|
||||
else if (segmentStyle == "capsule")
|
||||
control.segmentStyle = NSSegmentStyleCapsule;
|
||||
else if (segmentStyle == "small-square")
|
||||
control.segmentStyle = NSSegmentStyleSmallSquare;
|
||||
else if (segmentStyle == "separated")
|
||||
control.segmentStyle = NSSegmentStyleSeparated;
|
||||
else
|
||||
control.segmentStyle = NSSegmentStyleAutomatic;
|
||||
|
||||
std::string segmentMode;
|
||||
settings.Get("mode", &segmentMode);
|
||||
if (segmentMode == "multiple")
|
||||
control.trackingMode = NSSegmentSwitchTrackingSelectAny;
|
||||
else if (segmentMode == "buttons")
|
||||
control.trackingMode = NSSegmentSwitchTrackingMomentary;
|
||||
else
|
||||
control.trackingMode = NSSegmentSwitchTrackingSelectOne;
|
||||
|
||||
std::vector<mate::Dictionary> segments;
|
||||
settings.Get("segments", &segments);
|
||||
|
||||
control.segmentCount = segments.size();
|
||||
for (size_t i = 0; i < segments.size(); ++i) {
|
||||
std::string label;
|
||||
gfx::Image image;
|
||||
bool enabled = true;
|
||||
segments[i].Get("enabled", &enabled);
|
||||
if (segments[i].Get("label", &label)) {
|
||||
[control setLabel:base::SysUTF8ToNSString(label) forSegment:i];
|
||||
} else {
|
||||
[control setLabel:@"" forSegment:i];
|
||||
}
|
||||
if (segments[i].Get("icon", &image)) {
|
||||
[control setImage:image.AsNSImage() forSegment:i];
|
||||
[control setImageScaling:NSImageScaleProportionallyUpOrDown forSegment:i];
|
||||
} else {
|
||||
[control setImage:nil forSegment:i];
|
||||
}
|
||||
[control setEnabled:enabled forSegment:i];
|
||||
}
|
||||
|
||||
int selectedIndex = 0;
|
||||
settings.Get("selectedIndex", &selectedIndex);
|
||||
if (selectedIndex >= 0 && selectedIndex < control.segmentCount)
|
||||
control.selectedSegment = selectedIndex;
|
||||
}
|
||||
|
||||
- (NSTouchBarItem*)makeScrubberForID:(NSString*)id
|
||||
withIdentifier:(NSString*)identifier
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([id UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString(
|
||||
@"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]);
|
||||
|
||||
NSScrubber* scrubber = [[[NSClassFromString(@"NSScrubber") alloc]
|
||||
initWithFrame:NSZeroRect] autorelease];
|
||||
|
||||
[scrubber registerClass:NSClassFromString(@"NSScrubberTextItemView")
|
||||
forItemIdentifier:TextScrubberItemIdentifier];
|
||||
[scrubber registerClass:NSClassFromString(@"NSScrubberImageItemView")
|
||||
forItemIdentifier:ImageScrubberItemIdentifier];
|
||||
|
||||
scrubber.delegate = self;
|
||||
scrubber.dataSource = self;
|
||||
scrubber.identifier = id;
|
||||
|
||||
[item setView:scrubber];
|
||||
[self updateScrubber:item withSettings:settings];
|
||||
|
||||
return item.autorelease();
|
||||
}
|
||||
|
||||
- (void)updateScrubber:(NSCustomTouchBarItem*)item
|
||||
withSettings:(const mate::PersistentDictionary&)settings
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSScrubber* scrubber = item.view;
|
||||
|
||||
bool showsArrowButtons = false;
|
||||
settings.Get("showArrowButtons", &showsArrowButtons);
|
||||
scrubber.showsArrowButtons = showsArrowButtons;
|
||||
|
||||
std::string selectedStyle;
|
||||
std::string overlayStyle;
|
||||
settings.Get("selectedStyle", &selectedStyle);
|
||||
settings.Get("overlayStyle", &overlayStyle);
|
||||
|
||||
if (selectedStyle == "outline") {
|
||||
scrubber.selectionBackgroundStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
|
||||
} else if (selectedStyle == "background") {
|
||||
scrubber.selectionBackgroundStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
|
||||
} else {
|
||||
scrubber.selectionBackgroundStyle = nil;
|
||||
}
|
||||
|
||||
if (overlayStyle == "outline") {
|
||||
scrubber.selectionOverlayStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") outlineOverlayStyle];
|
||||
} else if (overlayStyle == "background") {
|
||||
scrubber.selectionOverlayStyle =
|
||||
[NSClassFromString(@"NSScrubberSelectionStyle") roundedBackgroundStyle];
|
||||
} else {
|
||||
scrubber.selectionOverlayStyle = nil;
|
||||
}
|
||||
|
||||
std::string mode;
|
||||
settings.Get("mode", &mode);
|
||||
if (mode == "fixed") {
|
||||
scrubber.mode = NSScrubberModeFixed;
|
||||
} else {
|
||||
scrubber.mode = NSScrubberModeFree;
|
||||
}
|
||||
|
||||
bool continuous = true;
|
||||
settings.Get("continuous", &continuous);
|
||||
scrubber.continuous = continuous;
|
||||
|
||||
[scrubber reloadData];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return 0;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
settings.Get("items", &items);
|
||||
return items.size();
|
||||
}
|
||||
|
||||
- (NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
|
||||
viewForItemAtIndex:(NSInteger)index
|
||||
API_AVAILABLE(macosx(10.12.2)) {
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!settings.Get("items", &items))
|
||||
return nil;
|
||||
|
||||
if (index >= static_cast<NSInteger>(items.size()))
|
||||
return nil;
|
||||
|
||||
mate::PersistentDictionary item = items[index];
|
||||
|
||||
NSScrubberItemView* itemView;
|
||||
std::string title;
|
||||
|
||||
if (item.Get("label", &title)) {
|
||||
NSScrubberTextItemView* view =
|
||||
[scrubber makeItemWithIdentifier:TextScrubberItemIdentifier owner:self];
|
||||
view.title = base::SysUTF8ToNSString(title);
|
||||
itemView = view;
|
||||
} else {
|
||||
NSScrubberImageItemView* view =
|
||||
[scrubber makeItemWithIdentifier:ImageScrubberItemIdentifier
|
||||
owner:self];
|
||||
gfx::Image image;
|
||||
if (item.Get("icon", &image)) {
|
||||
view.image = image.AsNSImage();
|
||||
}
|
||||
itemView = view;
|
||||
}
|
||||
|
||||
return itemView;
|
||||
}
|
||||
|
||||
- (NSSize)scrubber:(NSScrubber*)scrubber
|
||||
layout:(NSScrubberFlowLayout*)layout
|
||||
sizeForItemAtIndex:(NSInteger)itemIndex API_AVAILABLE(macosx(10.12.2)) {
|
||||
NSInteger width = 50;
|
||||
NSInteger height = 30;
|
||||
NSInteger margin = 15;
|
||||
NSSize defaultSize = NSMakeSize(width, height);
|
||||
|
||||
std::string s_id([[scrubber identifier] UTF8String]);
|
||||
if (![self hasItemWithID:s_id])
|
||||
return defaultSize;
|
||||
|
||||
mate::PersistentDictionary settings = settings_[s_id];
|
||||
std::vector<mate::PersistentDictionary> items;
|
||||
if (!settings.Get("items", &items))
|
||||
return defaultSize;
|
||||
|
||||
if (itemIndex >= static_cast<NSInteger>(items.size()))
|
||||
return defaultSize;
|
||||
|
||||
mate::PersistentDictionary item = items[itemIndex];
|
||||
std::string title;
|
||||
|
||||
if (item.Get("label", &title)) {
|
||||
NSSize size = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX);
|
||||
NSRect textRect = [base::SysUTF8ToNSString(title)
|
||||
boundingRectWithSize:size
|
||||
options:NSStringDrawingUsesLineFragmentOrigin |
|
||||
NSStringDrawingUsesFontLeading
|
||||
attributes:@{
|
||||
NSFontAttributeName : [NSFont systemFontOfSize:0]
|
||||
}];
|
||||
|
||||
width = textRect.size.width + margin;
|
||||
} else {
|
||||
gfx::Image image;
|
||||
if (item.Get("icon", &image)) {
|
||||
width = image.AsNSImage().size.width;
|
||||
}
|
||||
}
|
||||
|
||||
return NSMakeSize(width, height);
|
||||
}
|
||||
|
||||
@end
|
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
21
shell/browser/ui/cocoa/delayed_native_view_host.cc
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/delayed_native_view_host.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
DelayedNativeViewHost::DelayedNativeViewHost(gfx::NativeView native_view)
|
||||
: native_view_(native_view) {}
|
||||
|
||||
DelayedNativeViewHost::~DelayedNativeViewHost() {}
|
||||
|
||||
void DelayedNativeViewHost::ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) {
|
||||
NativeViewHost::ViewHierarchyChanged(details);
|
||||
if (details.is_add && GetWidget())
|
||||
Attach(native_view_);
|
||||
}
|
||||
|
||||
} // namespace atom
|
31
shell/browser/ui/cocoa/delayed_native_view_host.h
Normal file
31
shell/browser/ui/cocoa/delayed_native_view_host.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
||||
|
||||
#include "ui/views/controls/native/native_view_host.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
// Automatically attach the native view after the NativeViewHost is attached to
|
||||
// a widget. (Attaching it directly would cause crash.)
|
||||
class DelayedNativeViewHost : public views::NativeViewHost {
|
||||
public:
|
||||
explicit DelayedNativeViewHost(gfx::NativeView native_view);
|
||||
~DelayedNativeViewHost() override;
|
||||
|
||||
// views::View:
|
||||
void ViewHierarchyChanged(
|
||||
const views::ViewHierarchyChangedDetails& details) override;
|
||||
|
||||
private:
|
||||
gfx::NativeView native_view_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DelayedNativeViewHost);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_DELAYED_NATIVE_VIEW_HOST_H_
|
19
shell/browser/ui/cocoa/event_dispatching_window.h
Normal file
19
shell/browser/ui/cocoa/event_dispatching_window.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
||||
|
||||
#import "ui/base/cocoa/underlay_opengl_hosting_window.h"
|
||||
|
||||
@interface EventDispatchingWindow : UnderlayOpenGLHostingWindow {
|
||||
@private
|
||||
BOOL redispatchingEvent_;
|
||||
}
|
||||
|
||||
- (void)redispatchKeyEvent:(NSEvent*)event;
|
||||
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_EVENT_DISPATCHING_WINDOW_H_
|
34
shell/browser/ui/cocoa/event_dispatching_window.mm
Normal file
34
shell/browser/ui/cocoa/event_dispatching_window.mm
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/event_dispatching_window.h"
|
||||
|
||||
@implementation EventDispatchingWindow
|
||||
|
||||
- (void)sendEvent:(NSEvent*)event {
|
||||
if (!redispatchingEvent_)
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||
if (redispatchingEvent_)
|
||||
return NO;
|
||||
else
|
||||
return [super performKeyEquivalent:event];
|
||||
}
|
||||
|
||||
- (void)redispatchKeyEvent:(NSEvent*)event {
|
||||
NSEventType eventType = [event type];
|
||||
if (eventType != NSKeyDown && eventType != NSKeyUp &&
|
||||
eventType != NSFlagsChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Redispatch the event.
|
||||
redispatchingEvent_ = YES;
|
||||
[NSApp sendEvent:event];
|
||||
redispatchingEvent_ = NO;
|
||||
}
|
||||
|
||||
@end
|
33
shell/browser/ui/cocoa/root_view_mac.h
Normal file
33
shell/browser/ui/cocoa/root_view_mac.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
||||
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
class RootViewMac : public views::View {
|
||||
public:
|
||||
explicit RootViewMac(NativeWindow* window);
|
||||
~RootViewMac() override;
|
||||
|
||||
// views::View:
|
||||
void Layout() override;
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
|
||||
private:
|
||||
// Parent window, weak ref.
|
||||
NativeWindow* window_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RootViewMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ROOT_VIEW_MAC_H_
|
32
shell/browser/ui/cocoa/root_view_mac.mm
Normal file
32
shell/browser/ui/cocoa/root_view_mac.mm
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/root_view_mac.h"
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
RootViewMac::RootViewMac(NativeWindow* window) : window_(window) {
|
||||
set_owned_by_client();
|
||||
}
|
||||
|
||||
RootViewMac::~RootViewMac() {}
|
||||
|
||||
void RootViewMac::Layout() {
|
||||
if (!window_->content_view()) // Not ready yet.
|
||||
return;
|
||||
|
||||
window_->content_view()->SetBoundsRect(gfx::Rect(gfx::Point(), size()));
|
||||
}
|
||||
|
||||
gfx::Size RootViewMac::GetMinimumSize() const {
|
||||
return window_->GetMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size RootViewMac::GetMaximumSize() const {
|
||||
return window_->GetMaximumSize();
|
||||
}
|
||||
|
||||
} // namespace atom
|
267
shell/browser/ui/cocoa/touch_bar_forward_declarations.h
Normal file
267
shell/browser/ui/cocoa/touch_bar_forward_declarations.h
Normal file
|
@ -0,0 +1,267 @@
|
|||
// 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.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
||||
|
||||
// Once Chrome no longer supports OSX 10.12.0, this file can be deleted.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !defined(MAC_OS_X_VERSION_10_12_1)
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@class NSTouchBar, NSTouchBarItem;
|
||||
@class NSScrubber, NSScrubberItemView, NSScrubberArrangedView,
|
||||
NSScrubberTextItemView, NSScrubberImageItemView, NSScrubberSelectionStyle;
|
||||
@protocol NSTouchBarDelegate
|
||||
, NSScrubberDelegate, NSScrubberDataSource, NSScrubberFlowLayoutDelegate,
|
||||
NSScrubberFlowLayout;
|
||||
|
||||
typedef float NSTouchBarItemPriority;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityHigh = 1000;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityNormal = 0;
|
||||
static const NSTouchBarItemPriority NSTouchBarItemPriorityLow = -1000;
|
||||
|
||||
enum NSScrubberMode { NSScrubberModeFixed = 0, NSScrubberModeFree };
|
||||
|
||||
typedef NSString* NSTouchBarItemIdentifier;
|
||||
typedef NSString* NSTouchBarCustomizationIdentifier;
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceSmall =
|
||||
@"NSTouchBarItemIdentifierFixedSpaceSmall";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFixedSpaceLarge =
|
||||
@"NSTouchBarItemIdentifierFixedSpaceLarge";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierFlexibleSpace =
|
||||
@"NSTouchBarItemIdentifierFlexibleSpace";
|
||||
|
||||
static const NSTouchBarItemIdentifier NSTouchBarItemIdentifierOtherItemsProxy =
|
||||
@"NSTouchBarItemIdentifierOtherItemsProxy";
|
||||
|
||||
@interface NSTouchBar : NSObject <NSCoding>
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder*)aDecoder
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property(copy, nullable)
|
||||
NSTouchBarCustomizationIdentifier customizationIdentifier;
|
||||
@property(copy) NSArray* customizationAllowedItemIdentifiers;
|
||||
@property(copy) NSArray* customizationRequiredItemIdentifiers;
|
||||
@property(copy) NSArray* defaultItemIdentifiers;
|
||||
@property(copy, readonly) NSArray* itemIdentifiers;
|
||||
@property(copy, nullable) NSTouchBarItemIdentifier principalItemIdentifier;
|
||||
@property(copy, nullable)
|
||||
NSTouchBarItemIdentifier escapeKeyReplacementItemIdentifier;
|
||||
@property(copy) NSSet* templateItems;
|
||||
@property(nullable, weak) id<NSTouchBarDelegate> delegate;
|
||||
|
||||
- (nullable __kindof NSTouchBarItem*)itemForIdentifier:
|
||||
(NSTouchBarItemIdentifier)identifier;
|
||||
|
||||
@property(readonly, getter=isVisible) BOOL visible;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSTouchBarItem : NSObject <NSCoding>
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithCoder:(NSCoder*)coder
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@property(readonly, copy) NSTouchBarItemIdentifier identifier;
|
||||
@property NSTouchBarItemPriority visibilityPriority;
|
||||
@property(readonly, nullable) NSView* view;
|
||||
@property(readonly, nullable) NSViewController* viewController;
|
||||
@property(readwrite, copy) NSString* customizationLabel;
|
||||
@property(readonly, getter=isVisible) BOOL visible;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSGroupTouchBarItem : NSTouchBarItem
|
||||
|
||||
+ (NSGroupTouchBarItem*)groupItemWithIdentifier:
|
||||
(NSTouchBarItemIdentifier)identifier
|
||||
items:(NSArray*)items;
|
||||
|
||||
@property(strong) NSTouchBar* groupTouchBar;
|
||||
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSCustomTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property(readwrite, strong) __kindof NSView* view;
|
||||
@property(readwrite, strong, nullable)
|
||||
__kindof NSViewController* viewController;
|
||||
@property(readwrite, copy, null_resettable) NSString* customizationLabel;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSColorPickerTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property SEL action;
|
||||
@property(weak) id target;
|
||||
@property(copy) NSColor* color;
|
||||
@property(strong) NSColorList* colorList;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSPopoverTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property BOOL showsCloseButton;
|
||||
@property(strong) NSImage* collapsedRepresentationImage;
|
||||
@property(strong) NSString* collapsedRepresentationLabel;
|
||||
@property(strong) NSTouchBar* popoverTouchBar;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSSliderTouchBarItem : NSTouchBarItem
|
||||
|
||||
@property SEL action;
|
||||
@property(weak) id target;
|
||||
@property(copy) NSString* label;
|
||||
@property(strong) NSSlider* slider;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubber : NSView
|
||||
|
||||
@property(weak) id<NSScrubberDelegate> delegate;
|
||||
@property(weak) id<NSScrubberDataSource> dataSource;
|
||||
@property NSScrubberMode mode;
|
||||
@property BOOL showsArrowButtons;
|
||||
@property(getter=isContinuous) BOOL continuous;
|
||||
@property(strong, nullable) NSScrubberSelectionStyle* selectionBackgroundStyle;
|
||||
@property(strong, nullable) NSScrubberSelectionStyle* selectionOverlayStyle;
|
||||
|
||||
- (void)registerClass:(Class)itemViewClass
|
||||
forItemIdentifier:(NSString*)itemIdentifier;
|
||||
|
||||
- (__kindof NSScrubberItemView*)makeItemWithIdentifier:(NSString*)itemIdentifier
|
||||
owner:(id)owner;
|
||||
- (void)reloadData;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberFlowLayout : NSObject
|
||||
@end
|
||||
|
||||
@interface NSScrubberSelectionStyle : NSObject <NSCoding>
|
||||
|
||||
@property(class, strong, readonly)
|
||||
NSScrubberSelectionStyle* outlineOverlayStyle;
|
||||
@property(class, strong, readonly)
|
||||
NSScrubberSelectionStyle* roundedBackgroundStyle;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberArrangedView : NSView
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberItemView : NSScrubberArrangedView
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberTextItemView : NSScrubberItemView
|
||||
|
||||
@property(copy) NSString* title;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSScrubberImageItemView : NSScrubberItemView
|
||||
|
||||
@property(copy) NSImage* image;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSWindow (TouchBarSDK)
|
||||
|
||||
@property(strong, readwrite, nullable) NSTouchBar* touchBar;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSButton (TouchBarSDK)
|
||||
|
||||
@property(copy) NSColor* bezelColor;
|
||||
+ (instancetype)buttonWithTitle:(NSString*)title
|
||||
target:(id)target
|
||||
action:(SEL)action;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSTextField (TouchBarSDK)
|
||||
|
||||
+ (instancetype)labelWithString:(NSString*)stringValue;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSSegmentedControl (TouchBarSDK)
|
||||
|
||||
+ (instancetype)segmentedControlWithLabels:(NSArray*)labels
|
||||
trackingMode:(NSSegmentSwitchTracking)trackingMode
|
||||
target:(id)target
|
||||
action:(SEL)action;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSTouchBarDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (nullable NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar
|
||||
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberDelegate <NSObject>
|
||||
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didHighlightItemAtIndex:(NSInteger)highlightedIndex;
|
||||
- (void)scrubber:(NSScrubber*)scrubber
|
||||
didSelectItemAtIndex:(NSInteger)selectedIndex;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberDataSource <NSObject>
|
||||
|
||||
- (NSInteger)numberOfItemsForScrubber:(NSScrubber*)scrubber;
|
||||
- (__kindof NSScrubberItemView*)scrubber:(NSScrubber*)scrubber
|
||||
viewForItemAtIndex:(NSInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSScrubberFlowLayoutDelegate <NSObject>
|
||||
|
||||
- (NSSize)scrubber:(NSScrubber*)scrubber
|
||||
layout:(NSScrubberFlowLayout*)layout
|
||||
sizeForItemAtIndex:(NSInteger)itemIndex;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12_1
|
||||
|
||||
// When compiling against the 10.12.1 SDK or later, just provide forward
|
||||
// declarations to suppress the partial availability warnings.
|
||||
|
||||
@class NSCustomTouchBarItem;
|
||||
@class NSGroupTouchBarItem;
|
||||
@class NSTouchBar;
|
||||
@protocol NSTouchBarDelegate;
|
||||
@class NSTouchBarItem;
|
||||
|
||||
@interface NSWindow (TouchBarSDK)
|
||||
@property(strong, readonly) NSTouchBar* touchBar API_AVAILABLE(macosx(10.12.2));
|
||||
@end
|
||||
|
||||
#endif // MAC_OS_X_VERSION_10_12_1
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_TOUCH_BAR_FORWARD_DECLARATIONS_H_
|
30
shell/browser/ui/cocoa/views_delegate_mac.h
Normal file
30
shell/browser/ui/cocoa/views_delegate_mac.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
||||
|
||||
#include "ui/views/views_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class ViewsDelegateMac : public views::ViewsDelegate {
|
||||
public:
|
||||
ViewsDelegateMac();
|
||||
~ViewsDelegateMac() override;
|
||||
|
||||
// ViewsDelegate:
|
||||
void OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) override;
|
||||
ui::ContextFactory* GetContextFactory() override;
|
||||
ui::ContextFactoryPrivate* GetContextFactoryPrivate() override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ViewsDelegateMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_VIEWS_DELEGATE_MAC_H_
|
43
shell/browser/ui/cocoa/views_delegate_mac.mm
Normal file
43
shell/browser/ui/cocoa/views_delegate_mac.mm
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/cocoa/views_delegate_mac.h"
|
||||
|
||||
#include "content/public/browser/context_factory.h"
|
||||
#include "ui/views/widget/native_widget_mac.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
ViewsDelegateMac::ViewsDelegateMac() {}
|
||||
|
||||
ViewsDelegateMac::~ViewsDelegateMac() {}
|
||||
|
||||
void ViewsDelegateMac::OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) {
|
||||
// If we already have a native_widget, we don't have to try to come
|
||||
// up with one.
|
||||
if (params->native_widget)
|
||||
return;
|
||||
|
||||
if (!native_widget_factory().is_null()) {
|
||||
params->native_widget = native_widget_factory().Run(*params, delegate);
|
||||
if (params->native_widget)
|
||||
return;
|
||||
}
|
||||
|
||||
// Setting null here causes Widget to create the default NativeWidget
|
||||
// implementation.
|
||||
params->native_widget = nullptr;
|
||||
}
|
||||
|
||||
ui::ContextFactory* ViewsDelegateMac::GetContextFactory() {
|
||||
return content::GetContextFactory();
|
||||
}
|
||||
|
||||
ui::ContextFactoryPrivate* ViewsDelegateMac::GetContextFactoryPrivate() {
|
||||
return content::GetContextFactoryPrivate();
|
||||
}
|
||||
|
||||
} // namespace atom
|
125
shell/browser/ui/devtools_manager_delegate.cc
Normal file
125
shell/browser/ui/devtools_manager_delegate.cc
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/devtools_manager_delegate.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/atom_paths.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/devtools_frontend_host.h"
|
||||
#include "content/public/browser/devtools_socket_factory.h"
|
||||
#include "content/public/browser/favicon_status.h"
|
||||
#include "content/public/browser/navigation_entry.h"
|
||||
#include "content/public/common/content_switches.h"
|
||||
#include "content/public/common/url_constants.h"
|
||||
#include "content/public/common/user_agent.h"
|
||||
#include "electron/grit/electron_resources.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/socket/stream_socket.h"
|
||||
#include "net/socket/tcp_server_socket.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
class TCPServerSocketFactory : public content::DevToolsSocketFactory {
|
||||
public:
|
||||
TCPServerSocketFactory(const std::string& address, int port)
|
||||
: address_(address), port_(port) {}
|
||||
|
||||
private:
|
||||
// content::ServerSocketFactory.
|
||||
std::unique_ptr<net::ServerSocket> CreateForHttpServer() override {
|
||||
std::unique_ptr<net::ServerSocket> socket(
|
||||
new net::TCPServerSocket(nullptr, net::NetLogSource()));
|
||||
if (socket->ListenWithAddressAndPort(address_, port_, 10) != net::OK)
|
||||
return std::unique_ptr<net::ServerSocket>();
|
||||
|
||||
return socket;
|
||||
}
|
||||
std::unique_ptr<net::ServerSocket> CreateForTethering(
|
||||
std::string* name) override {
|
||||
return std::unique_ptr<net::ServerSocket>();
|
||||
}
|
||||
|
||||
std::string address_;
|
||||
uint16_t port_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory);
|
||||
};
|
||||
|
||||
std::unique_ptr<content::DevToolsSocketFactory> CreateSocketFactory() {
|
||||
auto& command_line = *base::CommandLine::ForCurrentProcess();
|
||||
// See if the user specified a port on the command line (useful for
|
||||
// automation). If not, use an ephemeral port by specifying 0.
|
||||
int port = 0;
|
||||
if (command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
|
||||
int temp_port;
|
||||
std::string port_str =
|
||||
command_line.GetSwitchValueASCII(switches::kRemoteDebuggingPort);
|
||||
if (base::StringToInt(port_str, &temp_port) && temp_port >= 0 &&
|
||||
temp_port < 65535) {
|
||||
port = temp_port;
|
||||
} else {
|
||||
DLOG(WARNING) << "Invalid http debugger port number " << temp_port;
|
||||
}
|
||||
}
|
||||
return std::unique_ptr<content::DevToolsSocketFactory>(
|
||||
new TCPServerSocketFactory("127.0.0.1", port));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// DevToolsManagerDelegate ---------------------------------------------------
|
||||
|
||||
// static
|
||||
void DevToolsManagerDelegate::StartHttpHandler() {
|
||||
base::FilePath user_dir;
|
||||
base::PathService::Get(DIR_USER_DATA, &user_dir);
|
||||
content::DevToolsAgentHost::StartRemoteDebuggingServer(
|
||||
CreateSocketFactory(), user_dir, base::FilePath());
|
||||
}
|
||||
|
||||
DevToolsManagerDelegate::DevToolsManagerDelegate() {}
|
||||
|
||||
DevToolsManagerDelegate::~DevToolsManagerDelegate() {}
|
||||
|
||||
void DevToolsManagerDelegate::Inspect(content::DevToolsAgentHost* agent_host) {}
|
||||
|
||||
void DevToolsManagerDelegate::HandleCommand(
|
||||
content::DevToolsAgentHost* agent_host,
|
||||
content::DevToolsAgentHostClient* client,
|
||||
const std::string& method,
|
||||
const std::string& message,
|
||||
NotHandledCallback callback) {
|
||||
std::move(callback).Run(message);
|
||||
}
|
||||
|
||||
scoped_refptr<content::DevToolsAgentHost>
|
||||
DevToolsManagerDelegate::CreateNewTarget(const GURL& url) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string DevToolsManagerDelegate::GetDiscoveryPageHTML() {
|
||||
return ui::ResourceBundle::GetSharedInstance()
|
||||
.GetRawDataResource(IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE)
|
||||
.as_string();
|
||||
}
|
||||
|
||||
bool DevToolsManagerDelegate::HasBundledFrontendResources() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace atom
|
42
shell/browser/ui/devtools_manager_delegate.h
Normal file
42
shell/browser/ui/devtools_manager_delegate.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/macros.h"
|
||||
#include "content/public/browser/devtools_manager_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
|
||||
public:
|
||||
static void StartHttpHandler();
|
||||
|
||||
DevToolsManagerDelegate();
|
||||
~DevToolsManagerDelegate() override;
|
||||
|
||||
// DevToolsManagerDelegate implementation.
|
||||
void Inspect(content::DevToolsAgentHost* agent_host) override;
|
||||
void HandleCommand(content::DevToolsAgentHost* agent_host,
|
||||
content::DevToolsAgentHostClient* client,
|
||||
const std::string& method,
|
||||
const std::string& message,
|
||||
NotHandledCallback callback) override;
|
||||
scoped_refptr<content::DevToolsAgentHost> CreateNewTarget(
|
||||
const GURL& url) override;
|
||||
std::string GetDiscoveryPageHTML() override;
|
||||
bool HasBundledFrontendResources() override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DevToolsManagerDelegate);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_DEVTOOLS_MANAGER_DELEGATE_H_
|
120
shell/browser/ui/devtools_ui.cc
Normal file
120
shell/browser/ui/devtools_ui.cc
Normal file
|
@ -0,0 +1,120 @@
|
|||
// Copyright (c) 2012 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-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/devtools_ui.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/ref_counted_memory.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "content/public/browser/devtools_frontend_host.h"
|
||||
#include "content/public/browser/url_data_source.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_ui.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kChromeUIDevToolsHost[] = "devtools";
|
||||
const char kChromeUIDevToolsBundledPath[] = "bundled";
|
||||
|
||||
std::string PathWithoutParams(const std::string& path) {
|
||||
return GURL(std::string("devtools://devtools/") + path).path().substr(1);
|
||||
}
|
||||
|
||||
std::string GetMimeTypeForPath(const std::string& path) {
|
||||
std::string filename = PathWithoutParams(path);
|
||||
if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "text/html";
|
||||
} else if (base::EndsWith(filename, ".css",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "text/css";
|
||||
} else if (base::EndsWith(filename, ".js",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "application/javascript";
|
||||
} else if (base::EndsWith(filename, ".png",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "image/png";
|
||||
} else if (base::EndsWith(filename, ".gif",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "image/gif";
|
||||
} else if (base::EndsWith(filename, ".svg",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "image/svg+xml";
|
||||
} else if (base::EndsWith(filename, ".manifest",
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
return "text/cache-manifest";
|
||||
}
|
||||
return "text/html";
|
||||
}
|
||||
|
||||
class BundledDataSource : public content::URLDataSource {
|
||||
public:
|
||||
BundledDataSource() {}
|
||||
~BundledDataSource() override {}
|
||||
|
||||
// content::URLDataSource implementation.
|
||||
std::string GetSource() const override { return kChromeUIDevToolsHost; }
|
||||
|
||||
void StartDataRequest(
|
||||
const std::string& path,
|
||||
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
|
||||
const GotDataCallback& callback) override {
|
||||
// Serve request from local bundle.
|
||||
std::string bundled_path_prefix(kChromeUIDevToolsBundledPath);
|
||||
bundled_path_prefix += "/";
|
||||
if (base::StartsWith(path, bundled_path_prefix,
|
||||
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||
StartBundledDataRequest(path.substr(bundled_path_prefix.length()),
|
||||
callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// We do not handle remote and custom requests.
|
||||
callback.Run(nullptr);
|
||||
}
|
||||
|
||||
std::string GetMimeType(const std::string& path) const override {
|
||||
return GetMimeTypeForPath(path);
|
||||
}
|
||||
|
||||
bool ShouldAddContentSecurityPolicy() const override { return false; }
|
||||
|
||||
bool ShouldDenyXFrameOptions() const override { return false; }
|
||||
|
||||
bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; }
|
||||
|
||||
void StartBundledDataRequest(const std::string& path,
|
||||
const GotDataCallback& callback) {
|
||||
std::string filename = PathWithoutParams(path);
|
||||
base::StringPiece resource =
|
||||
content::DevToolsFrontendHost::GetFrontendResource(filename);
|
||||
|
||||
DLOG_IF(WARNING, resource.empty())
|
||||
<< "Unable to find dev tool resource: " << filename
|
||||
<< ". If you compiled with debug_devtools=1, try running with "
|
||||
"--debug-devtools.";
|
||||
scoped_refptr<base::RefCountedStaticMemory> bytes(
|
||||
new base::RefCountedStaticMemory(resource.data(), resource.length()));
|
||||
callback.Run(bytes.get());
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(BundledDataSource);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
DevToolsUI::DevToolsUI(content::BrowserContext* browser_context,
|
||||
content::WebUI* web_ui)
|
||||
: WebUIController(web_ui) {
|
||||
web_ui->SetBindings(0);
|
||||
content::URLDataSource::Add(browser_context,
|
||||
std::make_unique<BundledDataSource>());
|
||||
}
|
||||
|
||||
} // namespace atom
|
27
shell/browser/ui/devtools_ui.h
Normal file
27
shell/browser/ui/devtools_ui.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2011 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-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_DEVTOOLS_UI_H_
|
||||
#define ATOM_BROWSER_UI_DEVTOOLS_UI_H_
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/web_ui_controller.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class BrowserContext;
|
||||
|
||||
class DevToolsUI : public content::WebUIController {
|
||||
public:
|
||||
explicit DevToolsUI(content::BrowserContext* browser_context,
|
||||
content::WebUI* web_ui);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(DevToolsUI);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_DEVTOOLS_UI_H_
|
24
shell/browser/ui/drag_util.h
Normal file
24
shell/browser/ui/drag_util.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_DRAG_UTIL_H_
|
||||
#define ATOM_BROWSER_UI_DRAG_UTIL_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
void DragFileItems(const std::vector<base::FilePath>& files,
|
||||
const gfx::Image& icon,
|
||||
gfx::NativeView view);
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_DRAG_UTIL_H_
|
61
shell/browser/ui/drag_util_mac.mm
Normal file
61
shell/browser/ui/drag_util_mac.mm
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/ui/drag_util.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// Write information about the file being dragged to the pasteboard.
|
||||
void AddFilesToPasteboard(NSPasteboard* pasteboard,
|
||||
const std::vector<base::FilePath>& files) {
|
||||
NSMutableArray* fileList = [NSMutableArray array];
|
||||
for (const base::FilePath& file : files)
|
||||
[fileList addObject:base::SysUTF8ToNSString(file.value())];
|
||||
[pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
|
||||
owner:nil];
|
||||
[pasteboard setPropertyList:fileList forType:NSFilenamesPboardType];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DragFileItems(const std::vector<base::FilePath>& files,
|
||||
const gfx::Image& icon,
|
||||
gfx::NativeView view) {
|
||||
NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
|
||||
AddFilesToPasteboard(pasteboard, files);
|
||||
|
||||
// Synthesize a drag event, since we don't have access to the actual event
|
||||
// that initiated a drag (possibly consumed by the Web UI, for example).
|
||||
NSWindow* window = [view.GetNativeNSView() window];
|
||||
NSPoint position = [window mouseLocationOutsideOfEventStream];
|
||||
NSTimeInterval eventTime = [[NSApp currentEvent] timestamp];
|
||||
NSEvent* dragEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
|
||||
location:position
|
||||
modifierFlags:NSLeftMouseDraggedMask
|
||||
timestamp:eventTime
|
||||
windowNumber:[window windowNumber]
|
||||
context:nil
|
||||
eventNumber:0
|
||||
clickCount:1
|
||||
pressure:1.0];
|
||||
|
||||
// Run the drag operation.
|
||||
[window dragImage:icon.ToNSImage()
|
||||
at:position
|
||||
offset:NSZeroSize
|
||||
event:dragEvent
|
||||
pasteboard:pasteboard
|
||||
source:view.GetNativeNSView()
|
||||
slideBack:YES];
|
||||
}
|
||||
|
||||
} // namespace atom
|
49
shell/browser/ui/drag_util_views.cc
Normal file
49
shell/browser/ui/drag_util_views.cc
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/drag_util.h"
|
||||
|
||||
#include "ui/aura/client/drag_drop_client.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/dragdrop/drag_drop_types.h"
|
||||
#include "ui/base/dragdrop/file_info.h"
|
||||
#include "ui/base/dragdrop/os_exchange_data.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
#include "ui/views/button_drag_utils.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
void DragFileItems(const std::vector<base::FilePath>& files,
|
||||
const gfx::Image& icon,
|
||||
gfx::NativeView view) {
|
||||
// Set up our OLE machinery
|
||||
ui::OSExchangeData data;
|
||||
|
||||
button_drag_utils::SetDragImage(
|
||||
GURL(), files[0].LossyDisplayName(), icon.AsImageSkia(), nullptr,
|
||||
*views::Widget::GetTopLevelWidgetForNativeView(view), &data);
|
||||
|
||||
std::vector<ui::FileInfo> file_infos;
|
||||
for (const base::FilePath& file : files) {
|
||||
file_infos.push_back(ui::FileInfo(file, base::FilePath()));
|
||||
}
|
||||
data.SetFilenames(file_infos);
|
||||
|
||||
aura::Window* root_window = view->GetRootWindow();
|
||||
if (!root_window || !aura::client::GetDragDropClient(root_window))
|
||||
return;
|
||||
|
||||
gfx::Point location = display::Screen::GetScreen()->GetCursorScreenPoint();
|
||||
// TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below.
|
||||
aura::client::GetDragDropClient(root_window)
|
||||
->StartDragAndDrop(
|
||||
data, root_window, view, location,
|
||||
ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK,
|
||||
ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE);
|
||||
}
|
||||
|
||||
} // namespace atom
|
69
shell/browser/ui/file_dialog.h
Normal file
69
shell/browser/ui/file_dialog.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_FILE_DIALOG_H_
|
||||
#define ATOM_BROWSER_UI_FILE_DIALOG_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindow;
|
||||
}
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
// <description, extensions>
|
||||
typedef std::pair<std::string, std::vector<std::string>> Filter;
|
||||
typedef std::vector<Filter> Filters;
|
||||
|
||||
enum FileDialogProperty {
|
||||
FILE_DIALOG_OPEN_FILE = 1 << 0,
|
||||
FILE_DIALOG_OPEN_DIRECTORY = 1 << 1,
|
||||
FILE_DIALOG_MULTI_SELECTIONS = 1 << 2,
|
||||
FILE_DIALOG_CREATE_DIRECTORY = 1 << 3,
|
||||
FILE_DIALOG_SHOW_HIDDEN_FILES = 1 << 4,
|
||||
FILE_DIALOG_PROMPT_TO_CREATE = 1 << 5,
|
||||
FILE_DIALOG_NO_RESOLVE_ALIASES = 1 << 6,
|
||||
FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY = 1 << 7,
|
||||
};
|
||||
|
||||
struct DialogSettings {
|
||||
atom::NativeWindow* parent_window = nullptr;
|
||||
std::string title;
|
||||
std::string message;
|
||||
std::string button_label;
|
||||
std::string name_field_label;
|
||||
base::FilePath default_path;
|
||||
Filters filters;
|
||||
int properties = 0;
|
||||
bool shows_tag_field = true;
|
||||
bool force_detached = false;
|
||||
bool security_scoped_bookmarks = false;
|
||||
|
||||
DialogSettings();
|
||||
DialogSettings(const DialogSettings&);
|
||||
~DialogSettings();
|
||||
};
|
||||
|
||||
bool ShowOpenDialogSync(const DialogSettings& settings,
|
||||
std::vector<base::FilePath>* paths);
|
||||
|
||||
void ShowOpenDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise);
|
||||
|
||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path);
|
||||
|
||||
void ShowSaveDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise);
|
||||
|
||||
} // namespace file_dialog
|
||||
|
||||
#endif // ATOM_BROWSER_UI_FILE_DIALOG_H_
|
311
shell/browser/ui/file_dialog_gtk.cc
Normal file
311
shell/browser/ui/file_dialog_gtk.cc
Normal file
|
@ -0,0 +1,311 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/file_dialog.h"
|
||||
|
||||
#include <glib/gi18n.h> // _() macro
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/unresponsive_suppressor.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "chrome/browser/ui/libgtkui/gtk_util.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
DialogSettings::DialogSettings() = default;
|
||||
DialogSettings::DialogSettings(const DialogSettings&) = default;
|
||||
DialogSettings::~DialogSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
static const int kPreviewWidth = 256;
|
||||
static const int kPreviewHeight = 512;
|
||||
|
||||
// Makes sure that .jpg also shows .JPG.
|
||||
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
|
||||
std::string* file_extension) {
|
||||
// Makes .* file extension matches all file types.
|
||||
if (*file_extension == ".*")
|
||||
return true;
|
||||
return base::EndsWith(file_info->filename, *file_extension,
|
||||
base::CompareCase::INSENSITIVE_ASCII);
|
||||
}
|
||||
|
||||
// Deletes |data| when gtk_file_filter_add_custom() is done with it.
|
||||
void OnFileFilterDataDestroyed(std::string* file_extension) {
|
||||
delete file_extension;
|
||||
}
|
||||
|
||||
class FileChooserDialog {
|
||||
public:
|
||||
FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings)
|
||||
: parent_(static_cast<atom::NativeWindowViews*>(settings.parent_window)),
|
||||
filters_(settings.filters) {
|
||||
const char* confirm_text = _("_OK");
|
||||
|
||||
if (!settings.button_label.empty())
|
||||
confirm_text = settings.button_label.c_str();
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
confirm_text = _("_Save");
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
confirm_text = _("_Open");
|
||||
|
||||
dialog_ = gtk_file_chooser_dialog_new(
|
||||
settings.title.c_str(), NULL, action, _("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
confirm_text, GTK_RESPONSE_ACCEPT, NULL);
|
||||
if (parent_) {
|
||||
parent_->SetEnabled(false);
|
||||
libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
|
||||
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
|
||||
}
|
||||
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog_),
|
||||
TRUE);
|
||||
if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog_), TRUE);
|
||||
|
||||
if (!settings.default_path.empty()) {
|
||||
if (base::DirectoryExists(settings.default_path)) {
|
||||
gtk_file_chooser_set_current_folder(
|
||||
GTK_FILE_CHOOSER(dialog_), settings.default_path.value().c_str());
|
||||
} else {
|
||||
if (settings.default_path.IsAbsolute()) {
|
||||
gtk_file_chooser_set_current_folder(
|
||||
GTK_FILE_CHOOSER(dialog_),
|
||||
settings.default_path.DirName().value().c_str());
|
||||
}
|
||||
|
||||
gtk_file_chooser_set_current_name(
|
||||
GTK_FILE_CHOOSER(dialog_),
|
||||
settings.default_path.BaseName().value().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.filters.empty())
|
||||
AddFilters(settings.filters);
|
||||
|
||||
preview_ = gtk_image_new();
|
||||
g_signal_connect(dialog_, "update-preview",
|
||||
G_CALLBACK(OnUpdatePreviewThunk), this);
|
||||
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog_), preview_);
|
||||
}
|
||||
|
||||
~FileChooserDialog() {
|
||||
gtk_widget_destroy(dialog_);
|
||||
if (parent_)
|
||||
parent_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void SetupProperties(int properties) {
|
||||
const auto hasProp = [properties](FileDialogProperty prop) {
|
||||
return gboolean((properties & prop) != 0);
|
||||
};
|
||||
auto* file_chooser = GTK_FILE_CHOOSER(dialog());
|
||||
gtk_file_chooser_set_select_multiple(file_chooser,
|
||||
hasProp(FILE_DIALOG_MULTI_SELECTIONS));
|
||||
gtk_file_chooser_set_show_hidden(file_chooser,
|
||||
hasProp(FILE_DIALOG_SHOW_HIDDEN_FILES));
|
||||
}
|
||||
|
||||
void RunAsynchronous() {
|
||||
g_signal_connect(dialog_, "delete-event",
|
||||
G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||||
g_signal_connect(dialog_, "response", G_CALLBACK(OnFileDialogResponseThunk),
|
||||
this);
|
||||
gtk_widget_show_all(dialog_);
|
||||
|
||||
// We need to call gtk_window_present after making the widgets visible to
|
||||
// make sure window gets correctly raised and gets focus.
|
||||
int time = ui::X11EventSource::GetInstance()->GetTimestamp();
|
||||
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
|
||||
}
|
||||
|
||||
void RunSaveAsynchronous(atom::util::Promise promise) {
|
||||
save_promise_.reset(new atom::util::Promise(std::move(promise)));
|
||||
RunAsynchronous();
|
||||
}
|
||||
|
||||
void RunOpenAsynchronous(atom::util::Promise promise) {
|
||||
open_promise_.reset(new atom::util::Promise(std::move(promise)));
|
||||
RunAsynchronous();
|
||||
}
|
||||
|
||||
base::FilePath GetFileName() const {
|
||||
gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
|
||||
const base::FilePath path(filename);
|
||||
g_free(filename);
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<base::FilePath> GetFileNames() const {
|
||||
std::vector<base::FilePath> paths;
|
||||
auto* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_));
|
||||
for (auto* iter = filenames; iter != NULL; iter = iter->next) {
|
||||
auto* filename = static_cast<char*>(iter->data);
|
||||
paths.emplace_back(filename);
|
||||
g_free(filename);
|
||||
}
|
||||
g_slist_free(filenames);
|
||||
return paths;
|
||||
}
|
||||
|
||||
CHROMEG_CALLBACK_1(FileChooserDialog,
|
||||
void,
|
||||
OnFileDialogResponse,
|
||||
GtkWidget*,
|
||||
int);
|
||||
|
||||
GtkWidget* dialog() const { return dialog_; }
|
||||
|
||||
private:
|
||||
void AddFilters(const Filters& filters);
|
||||
|
||||
atom::NativeWindowViews* parent_;
|
||||
atom::UnresponsiveSuppressor unresponsive_suppressor_;
|
||||
|
||||
GtkWidget* dialog_;
|
||||
GtkWidget* preview_;
|
||||
|
||||
Filters filters_;
|
||||
std::unique_ptr<atom::util::Promise> save_promise_;
|
||||
std::unique_ptr<atom::util::Promise> open_promise_;
|
||||
|
||||
// Callback for when we update the preview for the selection.
|
||||
CHROMEG_CALLBACK_0(FileChooserDialog, void, OnUpdatePreview, GtkWidget*);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
|
||||
};
|
||||
|
||||
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
|
||||
gtk_widget_hide(dialog_);
|
||||
if (save_promise_) {
|
||||
mate::Dictionary dict =
|
||||
mate::Dictionary::CreateEmpty(save_promise_->isolate());
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
dict.Set("canceled", false);
|
||||
dict.Set("filePath", GetFileName());
|
||||
} else {
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePath", base::FilePath());
|
||||
}
|
||||
save_promise_->Resolve(dict.GetHandle());
|
||||
} else if (open_promise_) {
|
||||
mate::Dictionary dict =
|
||||
mate::Dictionary::CreateEmpty(open_promise_->isolate());
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
dict.Set("canceled", false);
|
||||
dict.Set("filePaths", GetFileNames());
|
||||
} else {
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePaths", std::vector<base::FilePath>());
|
||||
}
|
||||
open_promise_->Resolve(dict.GetHandle());
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
void FileChooserDialog::AddFilters(const Filters& filters) {
|
||||
for (size_t i = 0; i < filters.size(); ++i) {
|
||||
const Filter& filter = filters[i];
|
||||
GtkFileFilter* gtk_filter = gtk_file_filter_new();
|
||||
|
||||
for (size_t j = 0; j < filter.second.size(); ++j) {
|
||||
auto file_extension =
|
||||
std::make_unique<std::string>("." + filter.second[j]);
|
||||
gtk_file_filter_add_custom(
|
||||
gtk_filter, GTK_FILE_FILTER_FILENAME,
|
||||
reinterpret_cast<GtkFileFilterFunc>(FileFilterCaseInsensitive),
|
||||
file_extension.release(),
|
||||
reinterpret_cast<GDestroyNotify>(OnFileFilterDataDestroyed));
|
||||
}
|
||||
|
||||
gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
|
||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog_), gtk_filter);
|
||||
}
|
||||
}
|
||||
|
||||
void FileChooserDialog::OnUpdatePreview(GtkWidget* chooser) {
|
||||
gchar* filename =
|
||||
gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(chooser));
|
||||
if (!filename) {
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
|
||||
FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't attempt to open anything which isn't a regular file. If a named pipe,
|
||||
// this may hang. See https://crbug.com/534754.
|
||||
struct stat stat_buf;
|
||||
if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
|
||||
g_free(filename);
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
|
||||
FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will preserve the image's aspect ratio.
|
||||
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
|
||||
kPreviewHeight, nullptr);
|
||||
g_free(filename);
|
||||
if (pixbuf) {
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(chooser),
|
||||
pixbuf ? TRUE : FALSE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialogSync(const DialogSettings& settings,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog open_dialog(action, settings);
|
||||
open_dialog.SetupProperties(settings.properties);
|
||||
|
||||
gtk_widget_show_all(open_dialog.dialog());
|
||||
int response = gtk_dialog_run(GTK_DIALOG(open_dialog.dialog()));
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
*paths = open_dialog.GetFileNames();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowOpenDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
|
||||
open_dialog->SetupProperties(settings.properties);
|
||||
open_dialog->RunOpenAsynchronous(std::move(promise));
|
||||
}
|
||||
|
||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
|
||||
FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
|
||||
gtk_widget_show_all(save_dialog.dialog());
|
||||
int response = gtk_dialog_run(GTK_DIALOG(save_dialog.dialog()));
|
||||
if (response == GTK_RESPONSE_ACCEPT) {
|
||||
*path = save_dialog.GetFileName();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowSaveDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
FileChooserDialog* save_dialog =
|
||||
new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
|
||||
save_dialog->RunSaveAsynchronous(std::move(promise));
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
424
shell/browser/ui/file_dialog_mac.mm
Normal file
424
shell/browser/ui/file_dialog_mac.mm
Normal file
|
@ -0,0 +1,424 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/file_dialog.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <CoreServices/CoreServices.h>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/mac/foundation_util.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/mac/scoped_cftyperef.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
|
||||
@interface PopUpButtonHandler : NSObject
|
||||
|
||||
@property(nonatomic, assign) NSSavePanel* savePanel;
|
||||
@property(nonatomic, strong) NSArray* fileTypesList;
|
||||
|
||||
- (instancetype)initWithPanel:(NSSavePanel*)panel
|
||||
andTypesList:(NSArray*)typesList;
|
||||
- (void)selectFormat:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@implementation PopUpButtonHandler
|
||||
|
||||
@synthesize savePanel;
|
||||
@synthesize fileTypesList;
|
||||
|
||||
- (instancetype)initWithPanel:(NSSavePanel*)panel
|
||||
andTypesList:(NSArray*)typesList {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self setSavePanel:panel];
|
||||
[self setFileTypesList:typesList];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)selectFormat:(id)sender {
|
||||
NSPopUpButton* button = (NSPopUpButton*)sender;
|
||||
NSInteger selectedItemIndex = [button indexOfSelectedItem];
|
||||
NSArray* list = [self fileTypesList];
|
||||
NSArray* fileTypes = [list objectAtIndex:selectedItemIndex];
|
||||
|
||||
// If we meet a '*' file extension, we allow all the file types and no
|
||||
// need to set the specified file types.
|
||||
if ([fileTypes count] == 0 || [fileTypes containsObject:@"*"])
|
||||
[[self savePanel] setAllowedFileTypes:nil];
|
||||
else
|
||||
[[self savePanel] setAllowedFileTypes:fileTypes];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Manages the PopUpButtonHandler.
|
||||
@interface AtomAccessoryView : NSView
|
||||
@end
|
||||
|
||||
@implementation AtomAccessoryView
|
||||
|
||||
- (void)dealloc {
|
||||
auto* popupButton =
|
||||
static_cast<NSPopUpButton*>([[self subviews] objectAtIndex:1]);
|
||||
[[popupButton target] release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
DialogSettings::DialogSettings() = default;
|
||||
DialogSettings::DialogSettings(const DialogSettings&) = default;
|
||||
DialogSettings::~DialogSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
void SetAllowedFileTypes(NSSavePanel* dialog, const Filters& filters) {
|
||||
NSMutableArray* file_types_list = [NSMutableArray array];
|
||||
NSMutableArray* filter_names = [NSMutableArray array];
|
||||
|
||||
// Create array to keep file types and their name.
|
||||
for (const Filter& filter : filters) {
|
||||
NSMutableSet* file_type_set = [NSMutableSet set];
|
||||
[filter_names addObject:@(filter.first.c_str())];
|
||||
for (const std::string& ext : filter.second) {
|
||||
[file_type_set addObject:@(ext.c_str())];
|
||||
}
|
||||
[file_types_list addObject:[file_type_set allObjects]];
|
||||
}
|
||||
|
||||
// Passing empty array to setAllowedFileTypes will cause exception.
|
||||
NSArray* file_types = nil;
|
||||
NSUInteger count = [file_types_list count];
|
||||
if (count > 0) {
|
||||
file_types = [[file_types_list objectAtIndex:0] allObjects];
|
||||
// If we meet a '*' file extension, we allow all the file types and no
|
||||
// need to set the specified file types.
|
||||
if ([file_types count] == 0 || [file_types containsObject:@"*"])
|
||||
file_types = nil;
|
||||
}
|
||||
[dialog setAllowedFileTypes:file_types];
|
||||
|
||||
if (count <= 1)
|
||||
return; // don't add file format picker
|
||||
|
||||
// Add file format picker.
|
||||
AtomAccessoryView* accessoryView =
|
||||
[[AtomAccessoryView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 200, 32.0)];
|
||||
NSTextField* label =
|
||||
[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)];
|
||||
|
||||
[label setEditable:NO];
|
||||
[label setStringValue:@"Format:"];
|
||||
[label setBordered:NO];
|
||||
[label setBezeled:NO];
|
||||
[label setDrawsBackground:NO];
|
||||
|
||||
NSPopUpButton* popupButton =
|
||||
[[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 140, 22.0)
|
||||
pullsDown:NO];
|
||||
PopUpButtonHandler* popUpButtonHandler =
|
||||
[[PopUpButtonHandler alloc] initWithPanel:dialog
|
||||
andTypesList:file_types_list];
|
||||
[popupButton addItemsWithTitles:filter_names];
|
||||
[popupButton setTarget:popUpButtonHandler];
|
||||
[popupButton setAction:@selector(selectFormat:)];
|
||||
|
||||
[accessoryView addSubview:[label autorelease]];
|
||||
[accessoryView addSubview:[popupButton autorelease]];
|
||||
|
||||
[dialog setAccessoryView:[accessoryView autorelease]];
|
||||
}
|
||||
|
||||
void SetupDialog(NSSavePanel* dialog, const DialogSettings& settings) {
|
||||
if (!settings.title.empty())
|
||||
[dialog setTitle:base::SysUTF8ToNSString(settings.title)];
|
||||
|
||||
if (!settings.button_label.empty())
|
||||
[dialog setPrompt:base::SysUTF8ToNSString(settings.button_label)];
|
||||
|
||||
if (!settings.message.empty())
|
||||
[dialog setMessage:base::SysUTF8ToNSString(settings.message)];
|
||||
|
||||
if (!settings.name_field_label.empty())
|
||||
[dialog
|
||||
setNameFieldLabel:base::SysUTF8ToNSString(settings.name_field_label)];
|
||||
|
||||
[dialog setShowsTagField:settings.shows_tag_field];
|
||||
|
||||
NSString* default_dir = nil;
|
||||
NSString* default_filename = nil;
|
||||
if (!settings.default_path.empty()) {
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
if (base::DirectoryExists(settings.default_path)) {
|
||||
default_dir = base::SysUTF8ToNSString(settings.default_path.value());
|
||||
} else {
|
||||
if (settings.default_path.IsAbsolute()) {
|
||||
default_dir =
|
||||
base::SysUTF8ToNSString(settings.default_path.DirName().value());
|
||||
}
|
||||
|
||||
default_filename =
|
||||
base::SysUTF8ToNSString(settings.default_path.BaseName().value());
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.filters.empty()) {
|
||||
[dialog setAllowsOtherFileTypes:YES];
|
||||
} else {
|
||||
// Set setAllowedFileTypes before setNameFieldStringValue as it might
|
||||
// override the extension set using setNameFieldStringValue
|
||||
SetAllowedFileTypes(dialog, settings.filters);
|
||||
}
|
||||
|
||||
// Make sure the extension is always visible. Without this, the extension in
|
||||
// the default filename will not be used in the saved file.
|
||||
[dialog setExtensionHidden:NO];
|
||||
|
||||
if (default_dir)
|
||||
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
|
||||
if (default_filename)
|
||||
[dialog setNameFieldStringValue:default_filename];
|
||||
}
|
||||
|
||||
void SetupDialogForProperties(NSOpenPanel* dialog, int properties) {
|
||||
[dialog setCanChooseFiles:(properties & FILE_DIALOG_OPEN_FILE)];
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
[dialog setCanChooseDirectories:YES];
|
||||
if (properties & FILE_DIALOG_CREATE_DIRECTORY)
|
||||
[dialog setCanCreateDirectories:YES];
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
[dialog setAllowsMultipleSelection:YES];
|
||||
if (properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
|
||||
[dialog setShowsHiddenFiles:YES];
|
||||
if (properties & FILE_DIALOG_NO_RESOLVE_ALIASES)
|
||||
[dialog setResolvesAliases:NO];
|
||||
if (properties & FILE_DIALOG_TREAT_PACKAGE_APP_AS_DIRECTORY)
|
||||
[dialog setTreatsFilePackagesAsDirectories:YES];
|
||||
}
|
||||
|
||||
// Run modal dialog with parent window and return user's choice.
|
||||
int RunModalDialog(NSSavePanel* dialog, const DialogSettings& settings) {
|
||||
__block int chosen = NSFileHandlingPanelCancelButton;
|
||||
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
||||
settings.force_detached) {
|
||||
chosen = [dialog runModal];
|
||||
} else {
|
||||
NSWindow* window =
|
||||
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
||||
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger c) {
|
||||
chosen = c;
|
||||
[NSApp stopModal];
|
||||
}];
|
||||
[NSApp runModalForWindow:window];
|
||||
}
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
// Create bookmark data and serialise it into a base64 string.
|
||||
std::string GetBookmarkDataFromNSURL(NSURL* url) {
|
||||
// Create the file if it doesn't exist (necessary for NSSavePanel options).
|
||||
NSFileManager* defaultManager = [NSFileManager defaultManager];
|
||||
if (![defaultManager fileExistsAtPath:[url path]]) {
|
||||
[defaultManager createFileAtPath:[url path] contents:nil attributes:nil];
|
||||
}
|
||||
|
||||
NSError* error = nil;
|
||||
NSData* bookmarkData =
|
||||
[url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
|
||||
includingResourceValuesForKeys:nil
|
||||
relativeToURL:nil
|
||||
error:&error];
|
||||
if (error != nil) {
|
||||
// Send back an empty string if there was an error.
|
||||
return "";
|
||||
} else {
|
||||
// Encode NSData in base64 then convert to NSString.
|
||||
NSString* base64data = [[NSString alloc]
|
||||
initWithData:[bookmarkData base64EncodedDataWithOptions:0]
|
||||
encoding:NSUTF8StringEncoding];
|
||||
return base::SysNSStringToUTF8(base64data);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadDialogPathsWithBookmarks(NSOpenPanel* dialog,
|
||||
std::vector<base::FilePath>* paths,
|
||||
std::vector<std::string>* bookmarks) {
|
||||
NSArray* urls = [dialog URLs];
|
||||
for (NSURL* url in urls)
|
||||
if ([url isFileURL]) {
|
||||
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
|
||||
bookmarks->push_back(GetBookmarkDataFromNSURL(url));
|
||||
}
|
||||
}
|
||||
|
||||
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
|
||||
std::vector<std::string> ignored_bookmarks;
|
||||
ReadDialogPathsWithBookmarks(dialog, paths, &ignored_bookmarks);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialogSync(const DialogSettings& settings,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
DCHECK(paths);
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, settings);
|
||||
SetupDialogForProperties(dialog, settings.properties);
|
||||
|
||||
int chosen = RunModalDialog(dialog, settings);
|
||||
if (chosen == NSFileHandlingPanelCancelButton)
|
||||
return false;
|
||||
|
||||
ReadDialogPaths(dialog, paths);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenDialogCompletion(int chosen,
|
||||
NSOpenPanel* dialog,
|
||||
bool security_scoped_bookmarks,
|
||||
atom::util::Promise promise) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePaths", std::vector<base::FilePath>());
|
||||
#if defined(MAS_BUILD)
|
||||
dict.Set("bookmarks", std::vector<std::string>());
|
||||
#endif
|
||||
promise.Resolve(dict.GetHandle());
|
||||
} else {
|
||||
std::vector<base::FilePath> paths;
|
||||
dict.Set("canceled", false);
|
||||
#if defined(MAS_BUILD)
|
||||
std::vector<std::string> bookmarks;
|
||||
if (security_scoped_bookmarks)
|
||||
ReadDialogPathsWithBookmarks(dialog, &paths, &bookmarks);
|
||||
else
|
||||
ReadDialogPaths(dialog, &paths);
|
||||
dict.Set("filePaths", paths);
|
||||
dict.Set("bookmarks", bookmarks);
|
||||
#else
|
||||
ReadDialogPaths(dialog, &paths);
|
||||
dict.Set("filePaths", paths);
|
||||
#endif
|
||||
promise.Resolve(dict.GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
void ShowOpenDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, settings);
|
||||
SetupDialogForProperties(dialog, settings.properties);
|
||||
|
||||
// Capture the value of the security_scoped_bookmarks settings flag
|
||||
// and pass it to the completion handler.
|
||||
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
|
||||
|
||||
__block atom::util::Promise p = std::move(promise);
|
||||
|
||||
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
||||
settings.force_detached) {
|
||||
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
||||
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
||||
std::move(p));
|
||||
}];
|
||||
} else {
|
||||
NSWindow* window =
|
||||
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
||||
[dialog
|
||||
beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
OpenDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
||||
std::move(p));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
|
||||
DCHECK(path);
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, settings);
|
||||
|
||||
int chosen = RunModalDialog(dialog, settings);
|
||||
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
|
||||
return false;
|
||||
|
||||
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SaveDialogCompletion(int chosen,
|
||||
NSSavePanel* dialog,
|
||||
bool security_scoped_bookmarks,
|
||||
atom::util::Promise promise) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePath", base::FilePath());
|
||||
#if defined(MAS_BUILD)
|
||||
dict.Set("bookmark", "");
|
||||
#endif
|
||||
} else {
|
||||
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
||||
dict.Set("filePath", base::FilePath(path));
|
||||
dict.Set("canceled", false);
|
||||
#if defined(MAS_BUILD)
|
||||
std::string bookmark;
|
||||
if (security_scoped_bookmarks) {
|
||||
bookmark = GetBookmarkDataFromNSURL([dialog URL]);
|
||||
dict.Set("bookmark", bookmark);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
promise.Resolve(dict.GetHandle());
|
||||
}
|
||||
|
||||
void ShowSaveDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, settings);
|
||||
[dialog setCanSelectHiddenExtension:YES];
|
||||
|
||||
// Capture the value of the security_scoped_bookmarks settings flag
|
||||
// and pass it to the completion handler.
|
||||
bool security_scoped_bookmarks = settings.security_scoped_bookmarks;
|
||||
|
||||
__block atom::util::Promise p = std::move(promise);
|
||||
|
||||
if (!settings.parent_window || !settings.parent_window->GetNativeWindow() ||
|
||||
settings.force_detached) {
|
||||
[dialog beginWithCompletionHandler:^(NSInteger chosen) {
|
||||
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
||||
std::move(p));
|
||||
}];
|
||||
} else {
|
||||
NSWindow* window =
|
||||
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
||||
[dialog
|
||||
beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
SaveDialogCompletion(chosen, dialog, security_scoped_bookmarks,
|
||||
std::move(p));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
334
shell/browser/ui/file_dialog_win.cc
Normal file
334
shell/browser/ui/file_dialog_win.cc
Normal file
|
@ -0,0 +1,334 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/file_dialog.h"
|
||||
|
||||
#include <windows.h> // windows.h must be included first
|
||||
|
||||
#include <atlbase.h> // atlbase.h for CComPtr
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <shobjidl.h>
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/unresponsive_suppressor.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/i18n/case_conversion.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "base/win/registry.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
DialogSettings::DialogSettings() = default;
|
||||
DialogSettings::DialogSettings(const DialogSettings&) = default;
|
||||
DialogSettings::~DialogSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
// Distinguish directories from regular files.
|
||||
bool IsDirectory(const base::FilePath& path) {
|
||||
base::File::Info file_info;
|
||||
return base::GetFileInfo(path, &file_info) ? file_info.is_directory
|
||||
: path.EndsWithSeparator();
|
||||
}
|
||||
|
||||
void ConvertFilters(const Filters& filters,
|
||||
std::vector<std::wstring>* buffer,
|
||||
std::vector<COMDLG_FILTERSPEC>* filterspec) {
|
||||
if (filters.empty()) {
|
||||
COMDLG_FILTERSPEC spec = {L"All Files (*.*)", L"*.*"};
|
||||
filterspec->push_back(spec);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer->reserve(filters.size() * 2);
|
||||
for (size_t i = 0; i < filters.size(); ++i) {
|
||||
const Filter& filter = filters[i];
|
||||
|
||||
COMDLG_FILTERSPEC spec;
|
||||
buffer->push_back(base::UTF8ToWide(filter.first));
|
||||
spec.pszName = buffer->back().c_str();
|
||||
|
||||
std::vector<std::string> extensions(filter.second);
|
||||
for (size_t j = 0; j < extensions.size(); ++j)
|
||||
extensions[j].insert(0, "*.");
|
||||
buffer->push_back(base::UTF8ToWide(base::JoinString(extensions, ";")));
|
||||
spec.pszSpec = buffer->back().c_str();
|
||||
|
||||
filterspec->push_back(spec);
|
||||
}
|
||||
}
|
||||
|
||||
struct RunState {
|
||||
base::Thread* dialog_thread;
|
||||
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner;
|
||||
};
|
||||
|
||||
bool CreateDialogThread(RunState* run_state) {
|
||||
auto thread =
|
||||
std::make_unique<base::Thread>(ELECTRON_PRODUCT_NAME "FileDialogThread");
|
||||
thread->init_com_with_mta(false);
|
||||
if (!thread->Start())
|
||||
return false;
|
||||
|
||||
run_state->dialog_thread = thread.release();
|
||||
run_state->ui_task_runner = base::ThreadTaskRunnerHandle::Get();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnDialogOpened(atom::util::Promise promise,
|
||||
bool canceled,
|
||||
std::vector<base::FilePath> paths) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
dict.Set("canceled", canceled);
|
||||
dict.Set("filePaths", paths);
|
||||
promise.Resolve(dict.GetHandle());
|
||||
}
|
||||
|
||||
void RunOpenDialogInNewThread(const RunState& run_state,
|
||||
const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
std::vector<base::FilePath> paths;
|
||||
bool result = ShowOpenDialogSync(settings, &paths);
|
||||
run_state.ui_task_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&OnDialogOpened, std::move(promise), !result, paths));
|
||||
run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
|
||||
}
|
||||
|
||||
void OnSaveDialogDone(atom::util::Promise promise,
|
||||
bool canceled,
|
||||
const base::FilePath path) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
dict.Set("canceled", canceled);
|
||||
dict.Set("filePath", path);
|
||||
promise.Resolve(dict.GetHandle());
|
||||
}
|
||||
|
||||
void RunSaveDialogInNewThread(const RunState& run_state,
|
||||
const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
base::FilePath path;
|
||||
bool result = ShowSaveDialogSync(settings, &path);
|
||||
run_state.ui_task_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&OnSaveDialogDone, std::move(promise), !result, path));
|
||||
run_state.ui_task_runner->DeleteSoon(FROM_HERE, run_state.dialog_thread);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static HRESULT GetFileNameFromShellItem(IShellItem* pShellItem,
|
||||
SIGDN type,
|
||||
LPWSTR lpstr,
|
||||
size_t cchLength) {
|
||||
assert(pShellItem != NULL);
|
||||
|
||||
LPWSTR lpstrName = NULL;
|
||||
HRESULT hRet = pShellItem->GetDisplayName(type, &lpstrName);
|
||||
|
||||
if (SUCCEEDED(hRet)) {
|
||||
if (wcslen(lpstrName) < cchLength) {
|
||||
wcscpy_s(lpstr, cchLength, lpstrName);
|
||||
} else {
|
||||
NOTREACHED();
|
||||
hRet = DISP_E_BUFFERTOOSMALL;
|
||||
}
|
||||
|
||||
::CoTaskMemFree(lpstrName);
|
||||
}
|
||||
|
||||
return hRet;
|
||||
}
|
||||
|
||||
static void SetDefaultFolder(IFileDialog* dialog,
|
||||
const base::FilePath file_path) {
|
||||
std::wstring directory =
|
||||
IsDirectory(file_path) ? file_path.value() : file_path.DirName().value();
|
||||
|
||||
ATL::CComPtr<IShellItem> folder_item;
|
||||
HRESULT hr = SHCreateItemFromParsingName(directory.c_str(), NULL,
|
||||
IID_PPV_ARGS(&folder_item));
|
||||
if (SUCCEEDED(hr))
|
||||
dialog->SetFolder(folder_item);
|
||||
}
|
||||
|
||||
static HRESULT ShowFileDialog(IFileDialog* dialog,
|
||||
const DialogSettings& settings) {
|
||||
atom::UnresponsiveSuppressor suppressor;
|
||||
HWND parent_window =
|
||||
settings.parent_window
|
||||
? static_cast<atom::NativeWindowViews*>(settings.parent_window)
|
||||
->GetAcceleratedWidget()
|
||||
: NULL;
|
||||
|
||||
return dialog->Show(parent_window);
|
||||
}
|
||||
|
||||
static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
|
||||
std::wstring file_part;
|
||||
|
||||
if (!IsDirectory(settings.default_path))
|
||||
file_part = settings.default_path.BaseName().value();
|
||||
|
||||
dialog->SetFileName(file_part.c_str());
|
||||
|
||||
if (!settings.title.empty())
|
||||
dialog->SetTitle(base::UTF8ToUTF16(settings.title).c_str());
|
||||
|
||||
if (!settings.button_label.empty())
|
||||
dialog->SetOkButtonLabel(base::UTF8ToUTF16(settings.button_label).c_str());
|
||||
|
||||
std::vector<std::wstring> buffer;
|
||||
std::vector<COMDLG_FILTERSPEC> filterspec;
|
||||
ConvertFilters(settings.filters, &buffer, &filterspec);
|
||||
|
||||
if (!filterspec.empty()) {
|
||||
dialog->SetFileTypes(filterspec.size(), filterspec.data());
|
||||
}
|
||||
|
||||
// By default, *.* will be added to the file name if file type is "*.*". In
|
||||
// Electron, we disable it to make a better experience.
|
||||
//
|
||||
// From MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/
|
||||
// bb775970(v=vs.85).aspx
|
||||
//
|
||||
// If SetDefaultExtension is not called, the dialog will not update
|
||||
// automatically when user choose a new file type in the file dialog.
|
||||
//
|
||||
// We set file extension to the first none-wildcard extension to make
|
||||
// sure the dialog will update file extension automatically.
|
||||
for (size_t i = 0; i < filterspec.size(); ++i) {
|
||||
if (std::wstring(filterspec[i].pszSpec) != L"*.*") {
|
||||
// SetFileTypeIndex is regarded as one-based index.
|
||||
dialog->SetFileTypeIndex(i + 1);
|
||||
dialog->SetDefaultExtension(filterspec[i].pszSpec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.default_path.IsAbsolute()) {
|
||||
SetDefaultFolder(dialog, settings.default_path);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShowOpenDialogSync(const DialogSettings& settings,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
ATL::CComPtr<IFileOpenDialog> file_open_dialog;
|
||||
HRESULT hr = file_open_dialog.CoCreateInstance(CLSID_FileOpenDialog);
|
||||
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
DWORD options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
|
||||
if (settings.properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
options |= FOS_PICKFOLDERS;
|
||||
if (settings.properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
options |= FOS_ALLOWMULTISELECT;
|
||||
if (settings.properties & FILE_DIALOG_SHOW_HIDDEN_FILES)
|
||||
options |= FOS_FORCESHOWHIDDEN;
|
||||
if (settings.properties & FILE_DIALOG_PROMPT_TO_CREATE)
|
||||
options |= FOS_CREATEPROMPT;
|
||||
file_open_dialog->SetOptions(options);
|
||||
|
||||
ApplySettings(file_open_dialog, settings);
|
||||
hr = ShowFileDialog(file_open_dialog, settings);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
ATL::CComPtr<IShellItemArray> items;
|
||||
hr = file_open_dialog->GetResults(&items);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
ATL::CComPtr<IShellItem> item;
|
||||
DWORD count = 0;
|
||||
hr = items->GetCount(&count);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
paths->reserve(count);
|
||||
for (DWORD i = 0; i < count; ++i) {
|
||||
hr = items->GetItemAt(i, &item);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
wchar_t file_name[MAX_PATH];
|
||||
hr = GetFileNameFromShellItem(item, SIGDN_FILESYSPATH, file_name,
|
||||
base::size(file_name));
|
||||
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
paths->push_back(base::FilePath(file_name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowOpenDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
RunState run_state;
|
||||
if (!CreateDialogThread(&run_state)) {
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePaths", std::vector<base::FilePath>());
|
||||
promise.Resolve(dict.GetHandle());
|
||||
} else {
|
||||
run_state.dialog_thread->task_runner()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&RunOpenDialogInNewThread, run_state,
|
||||
settings, std::move(promise)));
|
||||
}
|
||||
}
|
||||
|
||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
|
||||
ATL::CComPtr<IFileSaveDialog> file_save_dialog;
|
||||
HRESULT hr = file_save_dialog.CoCreateInstance(CLSID_FileSaveDialog);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
file_save_dialog->SetOptions(FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST |
|
||||
FOS_OVERWRITEPROMPT);
|
||||
ApplySettings(file_save_dialog, settings);
|
||||
hr = ShowFileDialog(file_save_dialog, settings);
|
||||
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
CComPtr<IShellItem> pItem;
|
||||
hr = file_save_dialog->GetResult(&pItem);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
PWSTR result_path = nullptr;
|
||||
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &result_path);
|
||||
if (!SUCCEEDED(hr))
|
||||
return false;
|
||||
|
||||
*path = base::FilePath(result_path);
|
||||
CoTaskMemFree(result_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowSaveDialog(const DialogSettings& settings,
|
||||
atom::util::Promise promise) {
|
||||
RunState run_state;
|
||||
if (!CreateDialogThread(&run_state)) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(promise.isolate());
|
||||
dict.Set("canceled", true);
|
||||
dict.Set("filePath", base::FilePath());
|
||||
promise.Resolve(dict.GetHandle());
|
||||
} else {
|
||||
run_state.dialog_thread->task_runner()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&RunSaveDialogInNewThread, run_state,
|
||||
settings, std::move(promise)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
19
shell/browser/ui/inspectable_web_contents.cc
Normal file
19
shell/browser/ui/inspectable_web_contents.cc
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents.h"
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_impl.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
InspectableWebContents* InspectableWebContents::Create(
|
||||
content::WebContents* web_contents,
|
||||
PrefService* pref_service,
|
||||
bool is_guest) {
|
||||
return new InspectableWebContentsImpl(web_contents, pref_service, is_guest);
|
||||
}
|
||||
|
||||
} // namespace atom
|
66
shell/browser/ui/inspectable_web_contents.h
Normal file
66
shell/browser/ui/inspectable_web_contents.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "content/public/browser/web_contents.h"
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace content {
|
||||
class DevToolsAgentHost;
|
||||
}
|
||||
|
||||
class PrefService;
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsDelegate;
|
||||
class InspectableWebContentsView;
|
||||
|
||||
// TODO(zcbenz): Remove this abstract wrapper and rename
|
||||
// InspectableWebContentsImpl to InspectableWebContents instead.
|
||||
class InspectableWebContents {
|
||||
public:
|
||||
// The returned InspectableWebContents takes ownership of the passed-in
|
||||
// WebContents.
|
||||
static InspectableWebContents* Create(content::WebContents* web_contents,
|
||||
PrefService* pref_service,
|
||||
bool is_guest);
|
||||
|
||||
virtual ~InspectableWebContents() {}
|
||||
|
||||
virtual InspectableWebContentsView* GetView() const = 0;
|
||||
virtual content::WebContents* GetWebContents() const = 0;
|
||||
virtual content::WebContents* GetDevToolsWebContents() const = 0;
|
||||
|
||||
// The delegate manages its own life.
|
||||
virtual void SetDelegate(InspectableWebContentsDelegate* delegate) = 0;
|
||||
virtual InspectableWebContentsDelegate* GetDelegate() const = 0;
|
||||
|
||||
virtual bool IsGuest() const = 0;
|
||||
virtual void ReleaseWebContents() = 0;
|
||||
virtual void SetDevToolsWebContents(content::WebContents* devtools) = 0;
|
||||
virtual void SetDockState(const std::string& state) = 0;
|
||||
virtual void ShowDevTools(bool activate) = 0;
|
||||
virtual void CloseDevTools() = 0;
|
||||
virtual bool IsDevToolsViewShowing() = 0;
|
||||
virtual void AttachTo(scoped_refptr<content::DevToolsAgentHost>) = 0;
|
||||
virtual void Detach() = 0;
|
||||
virtual void CallClientFunction(const std::string& function_name,
|
||||
const base::Value* arg1 = nullptr,
|
||||
const base::Value* arg2 = nullptr,
|
||||
const base::Value* arg3 = nullptr) = 0;
|
||||
virtual void InspectElement(int x, int y) = 0;
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_H_
|
42
shell/browser/ui/inspectable_web_contents_delegate.h
Normal file
42
shell/browser/ui/inspectable_web_contents_delegate.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsDelegate {
|
||||
public:
|
||||
virtual ~InspectableWebContentsDelegate() {}
|
||||
|
||||
// Requested by WebContents of devtools.
|
||||
virtual void DevToolsReloadPage() {}
|
||||
virtual void DevToolsSaveToFile(const std::string& url,
|
||||
const std::string& content,
|
||||
bool save_as) {}
|
||||
virtual void DevToolsAppendToFile(const std::string& url,
|
||||
const std::string& content) {}
|
||||
virtual void DevToolsRequestFileSystems() {}
|
||||
virtual void DevToolsAddFileSystem(const std::string& type,
|
||||
const base::FilePath& file_system_path) {}
|
||||
virtual void DevToolsRemoveFileSystem(
|
||||
const base::FilePath& file_system_path) {}
|
||||
virtual void DevToolsIndexPath(int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& excluded_folders) {}
|
||||
virtual void DevToolsStopIndexing(int request_id) {}
|
||||
virtual void DevToolsSearchInPath(int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& query) {}
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_DELEGATE_H_
|
917
shell/browser/ui/inspectable_web_contents_impl.cc
Normal file
917
shell/browser/ui/inspectable_web_contents_impl.cc
Normal file
|
@ -0,0 +1,917 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_impl.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_delegate.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "atom/common/platform_util.h"
|
||||
#include "base/base64.h"
|
||||
#include "base/guid.h"
|
||||
#include "base/json/json_reader.h"
|
||||
#include "base/json/json_writer.h"
|
||||
#include "base/metrics/histogram.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/pattern.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "base/values.h"
|
||||
#include "components/prefs/pref_registry_simple.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "components/prefs/scoped_user_pref_update.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
#include "content/public/browser/host_zoom_map.h"
|
||||
#include "content/public/browser/navigation_handle.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "content/public/common/user_agent.h"
|
||||
#include "ipc/ipc_channel.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
|
||||
#include "third_party/blink/public/common/logging/logging_utils.h"
|
||||
#include "ui/display/display.h"
|
||||
#include "ui/display/screen.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
const double kPresetZoomFactors[] = {0.25, 0.333, 0.5, 0.666, 0.75, 0.9,
|
||||
1.0, 1.1, 1.25, 1.5, 1.75, 2.0,
|
||||
2.5, 3.0, 4.0, 5.0};
|
||||
|
||||
const char kChromeUIDevToolsURL[] =
|
||||
"devtools://devtools/bundled/devtools_app.html?"
|
||||
"remoteBase=%s&"
|
||||
"can_dock=%s&"
|
||||
"toolbarColor=rgba(223,223,223,1)&"
|
||||
"textColor=rgba(0,0,0,1)&"
|
||||
"experiments=true";
|
||||
const char kChromeUIDevToolsRemoteFrontendBase[] =
|
||||
"https://devtools-frontend.appspot.com/";
|
||||
const char kChromeUIDevToolsRemoteFrontendPath[] = "serve_file";
|
||||
|
||||
const char kDevToolsBoundsPref[] = "electron.devtools.bounds";
|
||||
const char kDevToolsZoomPref[] = "electron.devtools.zoom";
|
||||
const char kDevToolsPreferences[] = "electron.devtools.preferences";
|
||||
|
||||
const char kFrontendHostId[] = "id";
|
||||
const char kFrontendHostMethod[] = "method";
|
||||
const char kFrontendHostParams[] = "params";
|
||||
const char kTitleFormat[] = "Developer Tools - %s";
|
||||
|
||||
const size_t kMaxMessageChunkSize = IPC::Channel::kMaximumMessageSize / 4;
|
||||
|
||||
// Stores all instances of InspectableWebContentsImpl.
|
||||
InspectableWebContentsImpl::List g_web_contents_instances_;
|
||||
|
||||
base::Value RectToDictionary(const gfx::Rect& bounds) {
|
||||
base::Value dict(base::Value::Type::DICTIONARY);
|
||||
dict.SetKey("x", base::Value(bounds.x()));
|
||||
dict.SetKey("y", base::Value(bounds.y()));
|
||||
dict.SetKey("width", base::Value(bounds.width()));
|
||||
dict.SetKey("height", base::Value(bounds.height()));
|
||||
return dict;
|
||||
}
|
||||
|
||||
gfx::Rect DictionaryToRect(const base::Value* dict) {
|
||||
const base::Value* found = dict->FindKey("x");
|
||||
int x = found ? found->GetInt() : 0;
|
||||
|
||||
found = dict->FindKey("y");
|
||||
int y = found ? found->GetInt() : 0;
|
||||
|
||||
found = dict->FindKey("width");
|
||||
int width = found ? found->GetInt() : 800;
|
||||
|
||||
found = dict->FindKey("height");
|
||||
int height = found ? found->GetInt() : 600;
|
||||
|
||||
return gfx::Rect(x, y, width, height);
|
||||
}
|
||||
|
||||
bool IsPointInRect(const gfx::Point& point, const gfx::Rect& rect) {
|
||||
return point.x() > rect.x() && point.x() < (rect.width() + rect.x()) &&
|
||||
point.y() > rect.y() && point.y() < (rect.height() + rect.y());
|
||||
}
|
||||
|
||||
bool IsPointInScreen(const gfx::Point& point) {
|
||||
for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) {
|
||||
if (IsPointInRect(point, display.bounds()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetZoomLevelForWebContents(content::WebContents* web_contents,
|
||||
double level) {
|
||||
content::HostZoomMap::SetZoomLevel(web_contents, level);
|
||||
}
|
||||
|
||||
double GetNextZoomLevel(double level, bool out) {
|
||||
double factor = content::ZoomLevelToZoomFactor(level);
|
||||
size_t size = base::size(kPresetZoomFactors);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (!content::ZoomValuesEqual(kPresetZoomFactors[i], factor))
|
||||
continue;
|
||||
if (out && i > 0)
|
||||
return content::ZoomFactorToZoomLevel(kPresetZoomFactors[i - 1]);
|
||||
if (!out && i != size - 1)
|
||||
return content::ZoomFactorToZoomLevel(kPresetZoomFactors[i + 1]);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
GURL GetRemoteBaseURL() {
|
||||
return GURL(base::StringPrintf("%s%s/%s/",
|
||||
kChromeUIDevToolsRemoteFrontendBase,
|
||||
kChromeUIDevToolsRemoteFrontendPath,
|
||||
content::GetWebKitRevision().c_str()));
|
||||
}
|
||||
|
||||
GURL GetDevToolsURL(bool can_dock) {
|
||||
auto url_string = base::StringPrintf(kChromeUIDevToolsURL,
|
||||
GetRemoteBaseURL().spec().c_str(),
|
||||
can_dock ? "true" : "");
|
||||
return GURL(url_string);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class InspectableWebContentsImpl::NetworkResourceLoader
|
||||
: public network::SimpleURLLoaderStreamConsumer {
|
||||
public:
|
||||
NetworkResourceLoader(int stream_id,
|
||||
InspectableWebContentsImpl* bindings,
|
||||
std::unique_ptr<network::SimpleURLLoader> loader,
|
||||
network::mojom::URLLoaderFactory* url_loader_factory,
|
||||
const DispatchCallback& callback)
|
||||
: stream_id_(stream_id),
|
||||
bindings_(bindings),
|
||||
loader_(std::move(loader)),
|
||||
callback_(callback) {
|
||||
loader_->SetOnResponseStartedCallback(base::BindOnce(
|
||||
&NetworkResourceLoader::OnResponseStarted, base::Unretained(this)));
|
||||
loader_->DownloadAsStream(url_loader_factory, this);
|
||||
}
|
||||
|
||||
NetworkResourceLoader(const NetworkResourceLoader&) = delete;
|
||||
NetworkResourceLoader& operator=(const NetworkResourceLoader&) = delete;
|
||||
|
||||
private:
|
||||
void OnResponseStarted(const GURL& final_url,
|
||||
const network::ResourceResponseHead& response_head) {
|
||||
response_headers_ = response_head.headers;
|
||||
}
|
||||
|
||||
void OnDataReceived(base::StringPiece chunk,
|
||||
base::OnceClosure resume) override {
|
||||
base::Value chunkValue;
|
||||
|
||||
bool encoded = !base::IsStringUTF8(chunk);
|
||||
if (encoded) {
|
||||
std::string encoded_string;
|
||||
base::Base64Encode(chunk, &encoded_string);
|
||||
chunkValue = base::Value(std::move(encoded_string));
|
||||
} else {
|
||||
chunkValue = base::Value(chunk);
|
||||
}
|
||||
base::Value id(stream_id_);
|
||||
base::Value encodedValue(encoded);
|
||||
|
||||
bindings_->CallClientFunction("DevToolsAPI.streamWrite", &id, &chunkValue,
|
||||
&encodedValue);
|
||||
std::move(resume).Run();
|
||||
}
|
||||
|
||||
void OnComplete(bool success) override {
|
||||
base::DictionaryValue response;
|
||||
response.SetInteger("statusCode", response_headers_
|
||||
? response_headers_->response_code()
|
||||
: 200);
|
||||
|
||||
auto headers = std::make_unique<base::DictionaryValue>();
|
||||
size_t iterator = 0;
|
||||
std::string name;
|
||||
std::string value;
|
||||
while (response_headers_ &&
|
||||
response_headers_->EnumerateHeaderLines(&iterator, &name, &value))
|
||||
headers->SetString(name, value);
|
||||
|
||||
response.Set("headers", std::move(headers));
|
||||
callback_.Run(&response);
|
||||
|
||||
bindings_->loaders_.erase(bindings_->loaders_.find(this));
|
||||
}
|
||||
|
||||
void OnRetry(base::OnceClosure start_retry) override {}
|
||||
|
||||
const int stream_id_;
|
||||
InspectableWebContentsImpl* const bindings_;
|
||||
std::unique_ptr<network::SimpleURLLoader> loader_;
|
||||
DispatchCallback callback_;
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers_;
|
||||
};
|
||||
|
||||
// Implemented separately on each platform.
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContentsImpl* inspectable_web_contents_impl);
|
||||
|
||||
// static
|
||||
const InspectableWebContentsImpl::List& InspectableWebContentsImpl::GetAll() {
|
||||
return g_web_contents_instances_;
|
||||
}
|
||||
|
||||
// static
|
||||
void InspectableWebContentsImpl::RegisterPrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterDictionaryPref(kDevToolsBoundsPref,
|
||||
RectToDictionary(gfx::Rect(0, 0, 800, 600)));
|
||||
registry->RegisterDoublePref(kDevToolsZoomPref, 0.);
|
||||
registry->RegisterDictionaryPref(kDevToolsPreferences);
|
||||
}
|
||||
|
||||
InspectableWebContentsImpl::InspectableWebContentsImpl(
|
||||
content::WebContents* web_contents,
|
||||
PrefService* pref_service,
|
||||
bool is_guest)
|
||||
: frontend_loaded_(false),
|
||||
can_dock_(true),
|
||||
delegate_(nullptr),
|
||||
pref_service_(pref_service),
|
||||
web_contents_(web_contents),
|
||||
is_guest_(is_guest),
|
||||
view_(CreateInspectableContentsView(this)),
|
||||
weak_factory_(this) {
|
||||
const base::Value* bounds_dict = pref_service_->Get(kDevToolsBoundsPref);
|
||||
if (bounds_dict->is_dict()) {
|
||||
devtools_bounds_ = DictionaryToRect(bounds_dict);
|
||||
// Sometimes the devtools window is out of screen or has too small size.
|
||||
if (devtools_bounds_.height() < 100 || devtools_bounds_.width() < 100) {
|
||||
devtools_bounds_.set_height(600);
|
||||
devtools_bounds_.set_width(800);
|
||||
}
|
||||
if (!IsPointInScreen(devtools_bounds_.origin())) {
|
||||
gfx::Rect display;
|
||||
if (!is_guest && web_contents->GetNativeView()) {
|
||||
display = display::Screen::GetScreen()
|
||||
->GetDisplayNearestView(web_contents->GetNativeView())
|
||||
.bounds();
|
||||
} else {
|
||||
display = display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
|
||||
}
|
||||
|
||||
devtools_bounds_.set_x(display.x() +
|
||||
(display.width() - devtools_bounds_.width()) / 2);
|
||||
devtools_bounds_.set_y(
|
||||
display.y() + (display.height() - devtools_bounds_.height()) / 2);
|
||||
}
|
||||
}
|
||||
g_web_contents_instances_.push_back(this);
|
||||
}
|
||||
|
||||
InspectableWebContentsImpl::~InspectableWebContentsImpl() {
|
||||
g_web_contents_instances_.remove(this);
|
||||
// Unsubscribe from devtools and Clean up resources.
|
||||
if (GetDevToolsWebContents()) {
|
||||
if (managed_devtools_web_contents_)
|
||||
managed_devtools_web_contents_->SetDelegate(nullptr);
|
||||
// Calling this also unsubscribes the observer, so WebContentsDestroyed
|
||||
// won't be called again.
|
||||
WebContentsDestroyed();
|
||||
}
|
||||
// Let destructor destroy managed_devtools_web_contents_.
|
||||
}
|
||||
|
||||
InspectableWebContentsView* InspectableWebContentsImpl::GetView() const {
|
||||
return view_.get();
|
||||
}
|
||||
|
||||
content::WebContents* InspectableWebContentsImpl::GetWebContents() const {
|
||||
return web_contents_.get();
|
||||
}
|
||||
|
||||
content::WebContents* InspectableWebContentsImpl::GetDevToolsWebContents()
|
||||
const {
|
||||
if (external_devtools_web_contents_)
|
||||
return external_devtools_web_contents_;
|
||||
else
|
||||
return managed_devtools_web_contents_.get();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::InspectElement(int x, int y) {
|
||||
if (agent_host_)
|
||||
agent_host_->InspectElement(web_contents_->GetMainFrame(), x, y);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetDelegate(
|
||||
InspectableWebContentsDelegate* delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
|
||||
InspectableWebContentsDelegate* InspectableWebContentsImpl::GetDelegate()
|
||||
const {
|
||||
return delegate_;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsImpl::IsGuest() const {
|
||||
return is_guest_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ReleaseWebContents() {
|
||||
web_contents_.release();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetDockState(const std::string& state) {
|
||||
if (state == "detach") {
|
||||
can_dock_ = false;
|
||||
} else {
|
||||
can_dock_ = true;
|
||||
dock_state_ = state;
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetDevToolsWebContents(
|
||||
content::WebContents* devtools) {
|
||||
if (!managed_devtools_web_contents_)
|
||||
external_devtools_web_contents_ = devtools;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ShowDevTools(bool activate) {
|
||||
if (embedder_message_dispatcher_) {
|
||||
if (managed_devtools_web_contents_)
|
||||
view_->ShowDevTools(activate);
|
||||
return;
|
||||
}
|
||||
|
||||
activate_ = activate;
|
||||
|
||||
// Show devtools only after it has done loading, this is to make sure the
|
||||
// SetIsDocked is called *BEFORE* ShowDevTools.
|
||||
embedder_message_dispatcher_ =
|
||||
DevToolsEmbedderMessageDispatcher::CreateForDevToolsFrontend(this);
|
||||
|
||||
if (!external_devtools_web_contents_) { // no external devtools
|
||||
managed_devtools_web_contents_ = content::WebContents::Create(
|
||||
content::WebContents::CreateParams(web_contents_->GetBrowserContext()));
|
||||
managed_devtools_web_contents_->SetDelegate(this);
|
||||
}
|
||||
|
||||
Observe(GetDevToolsWebContents());
|
||||
AttachTo(content::DevToolsAgentHost::GetOrCreateFor(web_contents_.get()));
|
||||
|
||||
GetDevToolsWebContents()->GetController().LoadURL(
|
||||
GetDevToolsURL(can_dock_), content::Referrer(),
|
||||
ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::CloseDevTools() {
|
||||
if (GetDevToolsWebContents()) {
|
||||
frontend_loaded_ = false;
|
||||
if (managed_devtools_web_contents_) {
|
||||
view_->CloseDevTools();
|
||||
managed_devtools_web_contents_.reset();
|
||||
}
|
||||
embedder_message_dispatcher_.reset();
|
||||
if (!IsGuest())
|
||||
web_contents_->Focus();
|
||||
}
|
||||
}
|
||||
|
||||
bool InspectableWebContentsImpl::IsDevToolsViewShowing() {
|
||||
return managed_devtools_web_contents_ && view_->IsDevToolsViewShowing();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::AttachTo(
|
||||
scoped_refptr<content::DevToolsAgentHost> host) {
|
||||
Detach();
|
||||
agent_host_ = std::move(host);
|
||||
// We could use ForceAttachClient here if problem arises with
|
||||
// devtools multiple session support.
|
||||
agent_host_->AttachClient(this);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::Detach() {
|
||||
if (agent_host_)
|
||||
agent_host_->DetachClient(this);
|
||||
agent_host_ = nullptr;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::Reattach(const DispatchCallback& callback) {
|
||||
if (agent_host_) {
|
||||
agent_host_->DetachClient(this);
|
||||
agent_host_->AttachClient(this);
|
||||
}
|
||||
callback.Run(nullptr);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::CallClientFunction(
|
||||
const std::string& function_name,
|
||||
const base::Value* arg1,
|
||||
const base::Value* arg2,
|
||||
const base::Value* arg3) {
|
||||
if (!GetDevToolsWebContents())
|
||||
return;
|
||||
|
||||
std::string javascript = function_name + "(";
|
||||
if (arg1) {
|
||||
std::string json;
|
||||
base::JSONWriter::Write(*arg1, &json);
|
||||
javascript.append(json);
|
||||
if (arg2) {
|
||||
base::JSONWriter::Write(*arg2, &json);
|
||||
javascript.append(", ").append(json);
|
||||
if (arg3) {
|
||||
base::JSONWriter::Write(*arg3, &json);
|
||||
javascript.append(", ").append(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
javascript.append(");");
|
||||
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
|
||||
base::UTF8ToUTF16(javascript), base::NullCallback());
|
||||
}
|
||||
|
||||
gfx::Rect InspectableWebContentsImpl::GetDevToolsBounds() const {
|
||||
return devtools_bounds_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SaveDevToolsBounds(const gfx::Rect& bounds) {
|
||||
pref_service_->Set(kDevToolsBoundsPref, RectToDictionary(bounds));
|
||||
devtools_bounds_ = bounds;
|
||||
}
|
||||
|
||||
double InspectableWebContentsImpl::GetDevToolsZoomLevel() const {
|
||||
return pref_service_->GetDouble(kDevToolsZoomPref);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::UpdateDevToolsZoomLevel(double level) {
|
||||
pref_service_->SetDouble(kDevToolsZoomPref, level);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ActivateWindow() {
|
||||
// Set the zoom level.
|
||||
SetZoomLevelForWebContents(GetDevToolsWebContents(), GetDevToolsZoomLevel());
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::CloseWindow() {
|
||||
GetDevToolsWebContents()->DispatchBeforeUnload(false /* auto_cancel */);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::LoadCompleted() {
|
||||
frontend_loaded_ = true;
|
||||
if (managed_devtools_web_contents_)
|
||||
view_->ShowDevTools(activate_);
|
||||
|
||||
// If the devtools can dock, "SetIsDocked" will be called by devtools itself.
|
||||
if (!can_dock_) {
|
||||
SetIsDocked(DispatchCallback(), false);
|
||||
} else {
|
||||
if (dock_state_.empty()) {
|
||||
const base::DictionaryValue* prefs =
|
||||
pref_service_->GetDictionary(kDevToolsPreferences);
|
||||
std::string current_dock_state;
|
||||
prefs->GetString("currentDockState", ¤t_dock_state);
|
||||
base::RemoveChars(current_dock_state, "\"", &dock_state_);
|
||||
}
|
||||
base::string16 javascript = base::UTF8ToUTF16(
|
||||
"Components.dockController.setDockSide(\"" + dock_state_ + "\");");
|
||||
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
|
||||
javascript, base::NullCallback());
|
||||
}
|
||||
|
||||
if (view_->GetDelegate())
|
||||
view_->GetDelegate()->DevToolsOpened();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetInspectedPageBounds(const gfx::Rect& rect) {
|
||||
DevToolsContentsResizingStrategy strategy(rect);
|
||||
if (contents_resizing_strategy_.Equals(strategy))
|
||||
return;
|
||||
|
||||
contents_resizing_strategy_.CopyFrom(strategy);
|
||||
if (managed_devtools_web_contents_)
|
||||
view_->SetContentsResizingStrategy(contents_resizing_strategy_);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::InspectElementCompleted() {}
|
||||
|
||||
void InspectableWebContentsImpl::InspectedURLChanged(const std::string& url) {
|
||||
if (managed_devtools_web_contents_)
|
||||
view_->SetTitle(
|
||||
base::UTF8ToUTF16(base::StringPrintf(kTitleFormat, url.c_str())));
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::LoadNetworkResource(
|
||||
const DispatchCallback& callback,
|
||||
const std::string& url,
|
||||
const std::string& headers,
|
||||
int stream_id) {
|
||||
GURL gurl(url);
|
||||
if (!gurl.is_valid()) {
|
||||
base::DictionaryValue response;
|
||||
response.SetInteger("statusCode", 404);
|
||||
callback.Run(&response);
|
||||
return;
|
||||
}
|
||||
|
||||
auto resource_request = std::make_unique<network::ResourceRequest>();
|
||||
resource_request->url = gurl;
|
||||
resource_request->headers.AddHeadersFromString(headers);
|
||||
|
||||
auto* partition = content::BrowserContext::GetDefaultStoragePartition(
|
||||
GetDevToolsWebContents()->GetBrowserContext());
|
||||
auto factory = partition->GetURLLoaderFactoryForBrowserProcess();
|
||||
|
||||
auto simple_url_loader = network::SimpleURLLoader::Create(
|
||||
std::move(resource_request), NO_TRAFFIC_ANNOTATION_YET);
|
||||
auto resource_loader = std::make_unique<NetworkResourceLoader>(
|
||||
stream_id, this, std::move(simple_url_loader), factory.get(), callback);
|
||||
loaders_.insert(std::move(resource_loader));
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetIsDocked(const DispatchCallback& callback,
|
||||
bool docked) {
|
||||
if (managed_devtools_web_contents_)
|
||||
view_->SetIsDocked(docked, activate_);
|
||||
if (!callback.is_null())
|
||||
callback.Run(nullptr);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::OpenInNewTab(const std::string& url) {}
|
||||
|
||||
void InspectableWebContentsImpl::ShowItemInFolder(
|
||||
const std::string& file_system_path) {
|
||||
if (file_system_path.empty())
|
||||
return;
|
||||
|
||||
base::FilePath path = base::FilePath::FromUTF8Unsafe(file_system_path);
|
||||
platform_util::OpenItem(path);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SaveToFile(const std::string& url,
|
||||
const std::string& content,
|
||||
bool save_as) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsSaveToFile(url, content, save_as);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::AppendToFile(const std::string& url,
|
||||
const std::string& content) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsAppendToFile(url, content);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::RequestFileSystems() {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsRequestFileSystems();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::AddFileSystem(const std::string& type) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsAddFileSystem(type, base::FilePath());
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::RemoveFileSystem(
|
||||
const std::string& file_system_path) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsRemoveFileSystem(
|
||||
base::FilePath::FromUTF8Unsafe(file_system_path));
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::UpgradeDraggedFileSystemPermissions(
|
||||
const std::string& file_system_url) {}
|
||||
|
||||
void InspectableWebContentsImpl::IndexPath(
|
||||
int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& excluded_folders) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsIndexPath(request_id, file_system_path,
|
||||
excluded_folders);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::StopIndexing(int request_id) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsStopIndexing(request_id);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SearchInPath(
|
||||
int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& query) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsSearchInPath(request_id, file_system_path, query);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetWhitelistedShortcuts(
|
||||
const std::string& message) {}
|
||||
|
||||
void InspectableWebContentsImpl::SetEyeDropperActive(bool active) {}
|
||||
void InspectableWebContentsImpl::ShowCertificateViewer(
|
||||
const std::string& cert_chain) {}
|
||||
|
||||
void InspectableWebContentsImpl::ZoomIn() {
|
||||
double new_level = GetNextZoomLevel(GetDevToolsZoomLevel(), false);
|
||||
SetZoomLevelForWebContents(GetDevToolsWebContents(), new_level);
|
||||
UpdateDevToolsZoomLevel(new_level);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ZoomOut() {
|
||||
double new_level = GetNextZoomLevel(GetDevToolsZoomLevel(), true);
|
||||
SetZoomLevelForWebContents(GetDevToolsWebContents(), new_level);
|
||||
UpdateDevToolsZoomLevel(new_level);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ResetZoom() {
|
||||
SetZoomLevelForWebContents(GetDevToolsWebContents(), 0.);
|
||||
UpdateDevToolsZoomLevel(0.);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetDevicesDiscoveryConfig(
|
||||
bool discover_usb_devices,
|
||||
bool port_forwarding_enabled,
|
||||
const std::string& port_forwarding_config,
|
||||
bool network_discovery_enabled,
|
||||
const std::string& network_discovery_config) {}
|
||||
|
||||
void InspectableWebContentsImpl::SetDevicesUpdatesEnabled(bool enabled) {}
|
||||
|
||||
void InspectableWebContentsImpl::PerformActionOnRemotePage(
|
||||
const std::string& page_id,
|
||||
const std::string& action) {}
|
||||
|
||||
void InspectableWebContentsImpl::OpenRemotePage(const std::string& browser_id,
|
||||
const std::string& url) {}
|
||||
|
||||
void InspectableWebContentsImpl::OpenNodeFrontend() {}
|
||||
|
||||
void InspectableWebContentsImpl::DispatchProtocolMessageFromDevToolsFrontend(
|
||||
const std::string& message) {
|
||||
// If the devtools wants to reload the page, hijack the message and handle it
|
||||
// to the delegate.
|
||||
if (base::MatchPattern(message,
|
||||
"{\"id\":*,"
|
||||
"\"method\":\"Page.reload\","
|
||||
"\"params\":*}")) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsReloadPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (agent_host_)
|
||||
agent_host_->DispatchProtocolMessage(this, message);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SendJsonRequest(
|
||||
const DispatchCallback& callback,
|
||||
const std::string& browser_id,
|
||||
const std::string& url) {
|
||||
callback.Run(nullptr);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::GetPreferences(
|
||||
const DispatchCallback& callback) {
|
||||
const base::DictionaryValue* prefs =
|
||||
pref_service_->GetDictionary(kDevToolsPreferences);
|
||||
callback.Run(prefs);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SetPreference(const std::string& name,
|
||||
const std::string& value) {
|
||||
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
|
||||
update.Get()->SetKey(name, base::Value(value));
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::RemovePreference(const std::string& name) {
|
||||
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
|
||||
update.Get()->RemoveWithoutPathExpansion(name, nullptr);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ClearPreferences() {
|
||||
DictionaryPrefUpdate update(pref_service_, kDevToolsPreferences);
|
||||
update.Get()->Clear();
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ConnectionReady() {}
|
||||
|
||||
void InspectableWebContentsImpl::RegisterExtensionsAPI(
|
||||
const std::string& origin,
|
||||
const std::string& script) {
|
||||
extensions_api_[origin + "/"] = script;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend(
|
||||
const std::string& message) {
|
||||
// TODO(alexeykuzmin): Should we expect it to exist?
|
||||
if (!embedder_message_dispatcher_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string method;
|
||||
base::ListValue empty_params;
|
||||
base::ListValue* params = &empty_params;
|
||||
|
||||
base::DictionaryValue* dict = nullptr;
|
||||
std::unique_ptr<base::Value> parsed_message(
|
||||
base::JSONReader::ReadDeprecated(message));
|
||||
if (!parsed_message || !parsed_message->GetAsDictionary(&dict) ||
|
||||
!dict->GetString(kFrontendHostMethod, &method) ||
|
||||
(dict->HasKey(kFrontendHostParams) &&
|
||||
!dict->GetList(kFrontendHostParams, ¶ms))) {
|
||||
LOG(ERROR) << "Invalid message was sent to embedder: " << message;
|
||||
return;
|
||||
}
|
||||
int id = 0;
|
||||
dict->GetInteger(kFrontendHostId, &id);
|
||||
embedder_message_dispatcher_->Dispatch(
|
||||
base::BindRepeating(&InspectableWebContentsImpl::SendMessageAck,
|
||||
weak_factory_.GetWeakPtr(), id),
|
||||
method, params);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::DispatchProtocolMessage(
|
||||
content::DevToolsAgentHost* agent_host,
|
||||
const std::string& message) {
|
||||
if (!frontend_loaded_)
|
||||
return;
|
||||
|
||||
if (message.length() < kMaxMessageChunkSize) {
|
||||
base::string16 javascript =
|
||||
base::UTF8ToUTF16("DevToolsAPI.dispatchMessage(" + message + ");");
|
||||
GetDevToolsWebContents()->GetMainFrame()->ExecuteJavaScript(
|
||||
javascript, base::NullCallback());
|
||||
return;
|
||||
}
|
||||
|
||||
base::Value total_size(static_cast<int>(message.length()));
|
||||
for (size_t pos = 0; pos < message.length(); pos += kMaxMessageChunkSize) {
|
||||
base::Value message_value(message.substr(pos, kMaxMessageChunkSize));
|
||||
CallClientFunction("DevToolsAPI.dispatchMessageChunk", &message_value,
|
||||
pos ? nullptr : &total_size, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::AgentHostClosed(
|
||||
content::DevToolsAgentHost* agent_host) {}
|
||||
|
||||
void InspectableWebContentsImpl::RenderFrameHostChanged(
|
||||
content::RenderFrameHost* old_host,
|
||||
content::RenderFrameHost* new_host) {
|
||||
if (new_host->GetParent())
|
||||
return;
|
||||
frontend_host_ = content::DevToolsFrontendHost::Create(
|
||||
new_host,
|
||||
base::BindRepeating(
|
||||
&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::WebContentsDestroyed() {
|
||||
frontend_loaded_ = false;
|
||||
external_devtools_web_contents_ = nullptr;
|
||||
Observe(nullptr);
|
||||
Detach();
|
||||
embedder_message_dispatcher_.reset();
|
||||
|
||||
if (view_ && view_->GetDelegate())
|
||||
view_->GetDelegate()->DevToolsClosed();
|
||||
}
|
||||
|
||||
bool InspectableWebContentsImpl::DidAddMessageToConsole(
|
||||
content::WebContents* source,
|
||||
blink::mojom::ConsoleMessageLevel level,
|
||||
const base::string16& message,
|
||||
int32_t line_no,
|
||||
const base::string16& source_id) {
|
||||
logging::LogMessage("CONSOLE", line_no,
|
||||
blink::ConsoleMessageLevelToLogSeverity(level))
|
||||
.stream()
|
||||
<< "\"" << message << "\", source: " << source_id << " (" << line_no
|
||||
<< ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsImpl::ShouldCreateWebContents(
|
||||
content::WebContents* web_contents,
|
||||
content::RenderFrameHost* opener,
|
||||
content::SiteInstance* source_site_instance,
|
||||
int32_t route_id,
|
||||
int32_t main_frame_route_id,
|
||||
int32_t main_frame_widget_route_id,
|
||||
content::mojom::WindowContainerType window_container_type,
|
||||
const GURL& opener_url,
|
||||
const std::string& frame_name,
|
||||
const GURL& target_url,
|
||||
const std::string& partition_id,
|
||||
content::SessionStorageNamespace* session_storage_namespace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsImpl::HandleKeyboardEvent(
|
||||
content::WebContents* source,
|
||||
const content::NativeWebKeyboardEvent& event) {
|
||||
auto* delegate = web_contents_->GetDelegate();
|
||||
return !delegate || delegate->HandleKeyboardEvent(source, event);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::CloseContents(content::WebContents* source) {
|
||||
// This is where the devtools closes itself (by clicking the x button).
|
||||
CloseDevTools();
|
||||
}
|
||||
|
||||
content::ColorChooser* InspectableWebContentsImpl::OpenColorChooser(
|
||||
content::WebContents* source,
|
||||
SkColor color,
|
||||
const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions) {
|
||||
auto* delegate = web_contents_->GetDelegate();
|
||||
if (delegate)
|
||||
return delegate->OpenColorChooser(source, color, suggestions);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
std::unique_ptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params) {
|
||||
auto* delegate = web_contents_->GetDelegate();
|
||||
if (delegate)
|
||||
delegate->RunFileChooser(render_frame_host, std::move(listener), params);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::EnumerateDirectory(
|
||||
content::WebContents* source,
|
||||
std::unique_ptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path) {
|
||||
auto* delegate = web_contents_->GetDelegate();
|
||||
if (delegate)
|
||||
delegate->EnumerateDirectory(source, std::move(listener), path);
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::OnWebContentsFocused(
|
||||
content::RenderWidgetHost* render_widget_host) {
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
if (view_->GetDelegate())
|
||||
view_->GetDelegate()->DevToolsFocused();
|
||||
#endif
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::ReadyToCommitNavigation(
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
if (navigation_handle->IsInMainFrame()) {
|
||||
if (navigation_handle->GetRenderFrameHost() ==
|
||||
GetDevToolsWebContents()->GetMainFrame() &&
|
||||
frontend_host_) {
|
||||
return;
|
||||
}
|
||||
frontend_host_ = content::DevToolsFrontendHost::Create(
|
||||
web_contents()->GetMainFrame(),
|
||||
base::BindRepeating(
|
||||
&InspectableWebContentsImpl::HandleMessageFromDevToolsFrontend,
|
||||
base::Unretained(this)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::DidFinishNavigation(
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
if (navigation_handle->IsInMainFrame() ||
|
||||
!navigation_handle->GetURL().SchemeIs("chrome-extension") ||
|
||||
!navigation_handle->HasCommitted())
|
||||
return;
|
||||
content::RenderFrameHost* frame = navigation_handle->GetRenderFrameHost();
|
||||
auto origin = navigation_handle->GetURL().GetOrigin().spec();
|
||||
auto it = extensions_api_.find(origin);
|
||||
if (it == extensions_api_.end())
|
||||
return;
|
||||
// Injected Script from devtools frontend doesn't expose chrome,
|
||||
// most likely bug in chromium.
|
||||
base::ReplaceFirstSubstringAfterOffset(&it->second, 0, "var chrome",
|
||||
"var chrome = window.chrome ");
|
||||
auto script = base::StringPrintf("%s(\"%s\")", it->second.c_str(),
|
||||
base::GenerateGUID().c_str());
|
||||
// Invoking content::DevToolsFrontendHost::SetupExtensionsAPI(frame, script);
|
||||
// should be enough, but it seems to be a noop currently.
|
||||
frame->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
|
||||
base::NullCallback());
|
||||
}
|
||||
|
||||
void InspectableWebContentsImpl::SendMessageAck(int request_id,
|
||||
const base::Value* arg) {
|
||||
base::Value id_value(request_id);
|
||||
CallClientFunction("DevToolsAPI.embedderMessageAck", &id_value, arg, nullptr);
|
||||
}
|
||||
|
||||
} // namespace atom
|
250
shell/browser/ui/inspectable_web_contents_impl.h
Normal file
250
shell/browser/ui/inspectable_web_contents_impl.h
Normal file
|
@ -0,0 +1,250 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents.h"
|
||||
#include "base/containers/unique_ptr_adapters.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "chrome/browser/devtools/devtools_embedder_message_dispatcher.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/devtools_frontend_host.h"
|
||||
#include "content/public/browser/web_contents_delegate.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
|
||||
class PrefService;
|
||||
class PrefRegistrySimple;
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsDelegate;
|
||||
class InspectableWebContentsView;
|
||||
|
||||
class InspectableWebContentsImpl
|
||||
: public InspectableWebContents,
|
||||
public content::DevToolsAgentHostClient,
|
||||
public content::WebContentsObserver,
|
||||
public content::WebContentsDelegate,
|
||||
public DevToolsEmbedderMessageDispatcher::Delegate {
|
||||
public:
|
||||
using List = std::list<InspectableWebContentsImpl*>;
|
||||
|
||||
static const List& GetAll();
|
||||
static void RegisterPrefs(PrefRegistrySimple* pref_registry);
|
||||
|
||||
InspectableWebContentsImpl(content::WebContents* web_contents,
|
||||
PrefService* pref_service,
|
||||
bool is_guest);
|
||||
~InspectableWebContentsImpl() override;
|
||||
|
||||
InspectableWebContentsView* GetView() const override;
|
||||
content::WebContents* GetWebContents() const override;
|
||||
content::WebContents* GetDevToolsWebContents() const override;
|
||||
|
||||
void SetDelegate(InspectableWebContentsDelegate* delegate) override;
|
||||
InspectableWebContentsDelegate* GetDelegate() const override;
|
||||
bool IsGuest() const override;
|
||||
void ReleaseWebContents() override;
|
||||
void SetDevToolsWebContents(content::WebContents* devtools) override;
|
||||
void SetDockState(const std::string& state) override;
|
||||
void ShowDevTools(bool activate) override;
|
||||
void CloseDevTools() override;
|
||||
bool IsDevToolsViewShowing() override;
|
||||
void AttachTo(scoped_refptr<content::DevToolsAgentHost>) override;
|
||||
void Detach() override;
|
||||
void CallClientFunction(const std::string& function_name,
|
||||
const base::Value* arg1,
|
||||
const base::Value* arg2,
|
||||
const base::Value* arg3) override;
|
||||
void InspectElement(int x, int y) override;
|
||||
|
||||
// Return the last position and size of devtools window.
|
||||
gfx::Rect GetDevToolsBounds() const;
|
||||
void SaveDevToolsBounds(const gfx::Rect& bounds);
|
||||
|
||||
// Return the last set zoom level of devtools window.
|
||||
double GetDevToolsZoomLevel() const;
|
||||
void UpdateDevToolsZoomLevel(double level);
|
||||
|
||||
private:
|
||||
// DevToolsEmbedderMessageDispacher::Delegate
|
||||
void ActivateWindow() override;
|
||||
void CloseWindow() override;
|
||||
void LoadCompleted() override;
|
||||
void SetInspectedPageBounds(const gfx::Rect& rect) override;
|
||||
void InspectElementCompleted() override;
|
||||
void InspectedURLChanged(const std::string& url) override;
|
||||
void LoadNetworkResource(const DispatchCallback& callback,
|
||||
const std::string& url,
|
||||
const std::string& headers,
|
||||
int stream_id) override;
|
||||
void SetIsDocked(const DispatchCallback& callback, bool is_docked) override;
|
||||
void OpenInNewTab(const std::string& url) override;
|
||||
void ShowItemInFolder(const std::string& file_system_path) override;
|
||||
void SaveToFile(const std::string& url,
|
||||
const std::string& content,
|
||||
bool save_as) override;
|
||||
void AppendToFile(const std::string& url,
|
||||
const std::string& content) override;
|
||||
void RequestFileSystems() override;
|
||||
void AddFileSystem(const std::string& type) override;
|
||||
void RemoveFileSystem(const std::string& file_system_path) override;
|
||||
void UpgradeDraggedFileSystemPermissions(
|
||||
const std::string& file_system_url) override;
|
||||
void IndexPath(int index_request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& excluded_folders) override;
|
||||
void StopIndexing(int index_request_id) override;
|
||||
void SearchInPath(int search_request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& query) override;
|
||||
void SetWhitelistedShortcuts(const std::string& message) override;
|
||||
void SetEyeDropperActive(bool active) override;
|
||||
void ShowCertificateViewer(const std::string& cert_chain) override;
|
||||
void ZoomIn() override;
|
||||
void ZoomOut() override;
|
||||
void ResetZoom() override;
|
||||
void SetDevicesDiscoveryConfig(
|
||||
bool discover_usb_devices,
|
||||
bool port_forwarding_enabled,
|
||||
const std::string& port_forwarding_config,
|
||||
bool network_discovery_enabled,
|
||||
const std::string& network_discovery_config) override;
|
||||
void SetDevicesUpdatesEnabled(bool enabled) override;
|
||||
void PerformActionOnRemotePage(const std::string& page_id,
|
||||
const std::string& action) override;
|
||||
void OpenRemotePage(const std::string& browser_id,
|
||||
const std::string& url) override;
|
||||
void OpenNodeFrontend() override;
|
||||
void DispatchProtocolMessageFromDevToolsFrontend(
|
||||
const std::string& message) override;
|
||||
void SendJsonRequest(const DispatchCallback& callback,
|
||||
const std::string& browser_id,
|
||||
const std::string& url) override;
|
||||
void GetPreferences(const DispatchCallback& callback) override;
|
||||
void SetPreference(const std::string& name,
|
||||
const std::string& value) override;
|
||||
void RemovePreference(const std::string& name) override;
|
||||
void ClearPreferences() override;
|
||||
void ConnectionReady() override;
|
||||
void RegisterExtensionsAPI(const std::string& origin,
|
||||
const std::string& script) override;
|
||||
void Reattach(const DispatchCallback& callback) override;
|
||||
void RecordEnumeratedHistogram(const std::string& name,
|
||||
int sample,
|
||||
int boundary_value) override {}
|
||||
void ReadyForTest() override {}
|
||||
void SetOpenNewWindowForPopups(bool value) override {}
|
||||
void RecordPerformanceHistogram(const std::string& name,
|
||||
double duration) override {}
|
||||
void RecordUserMetricsAction(const std::string& name) override {}
|
||||
|
||||
// content::DevToolsFrontendHostDelegate:
|
||||
void HandleMessageFromDevToolsFrontend(const std::string& message);
|
||||
|
||||
// content::DevToolsAgentHostClient:
|
||||
void DispatchProtocolMessage(content::DevToolsAgentHost* agent_host,
|
||||
const std::string& message) override;
|
||||
void AgentHostClosed(content::DevToolsAgentHost* agent_host) override;
|
||||
|
||||
// content::WebContentsObserver:
|
||||
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||
content::RenderFrameHost* new_host) override;
|
||||
void WebContentsDestroyed() override;
|
||||
void OnWebContentsFocused(
|
||||
content::RenderWidgetHost* render_widget_host) override;
|
||||
void ReadyToCommitNavigation(
|
||||
content::NavigationHandle* navigation_handle) override;
|
||||
void DidFinishNavigation(
|
||||
content::NavigationHandle* navigation_handle) override;
|
||||
|
||||
// content::WebContentsDelegate:
|
||||
bool DidAddMessageToConsole(content::WebContents* source,
|
||||
blink::mojom::ConsoleMessageLevel level,
|
||||
const base::string16& message,
|
||||
int32_t line_no,
|
||||
const base::string16& source_id) override;
|
||||
bool ShouldCreateWebContents(
|
||||
content::WebContents* web_contents,
|
||||
content::RenderFrameHost* opener,
|
||||
content::SiteInstance* source_site_instance,
|
||||
int32_t route_id,
|
||||
int32_t main_frame_route_id,
|
||||
int32_t main_frame_widget_route_id,
|
||||
content::mojom::WindowContainerType window_container_type,
|
||||
const GURL& opener_url,
|
||||
const std::string& frame_name,
|
||||
const GURL& target_url,
|
||||
const std::string& partition_id,
|
||||
content::SessionStorageNamespace* session_storage_namespace) override;
|
||||
bool HandleKeyboardEvent(content::WebContents*,
|
||||
const content::NativeWebKeyboardEvent&) override;
|
||||
void CloseContents(content::WebContents* source) override;
|
||||
content::ColorChooser* OpenColorChooser(
|
||||
content::WebContents* source,
|
||||
SkColor color,
|
||||
const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
|
||||
override;
|
||||
void RunFileChooser(content::RenderFrameHost* render_frame_host,
|
||||
std::unique_ptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params) override;
|
||||
void EnumerateDirectory(content::WebContents* source,
|
||||
std::unique_ptr<content::FileSelectListener> listener,
|
||||
const base::FilePath& path) override;
|
||||
|
||||
void SendMessageAck(int request_id, const base::Value* arg1);
|
||||
|
||||
bool frontend_loaded_;
|
||||
scoped_refptr<content::DevToolsAgentHost> agent_host_;
|
||||
std::unique_ptr<content::DevToolsFrontendHost> frontend_host_;
|
||||
std::unique_ptr<DevToolsEmbedderMessageDispatcher>
|
||||
embedder_message_dispatcher_;
|
||||
|
||||
DevToolsContentsResizingStrategy contents_resizing_strategy_;
|
||||
gfx::Rect devtools_bounds_;
|
||||
bool can_dock_;
|
||||
std::string dock_state_;
|
||||
bool activate_ = true;
|
||||
|
||||
InspectableWebContentsDelegate* delegate_; // weak references.
|
||||
|
||||
PrefService* pref_service_; // weak reference.
|
||||
|
||||
std::unique_ptr<content::WebContents> web_contents_;
|
||||
|
||||
// The default devtools created by this class when we don't have an external
|
||||
// one assigned by SetDevToolsWebContents.
|
||||
std::unique_ptr<content::WebContents> managed_devtools_web_contents_;
|
||||
// The external devtools assigned by SetDevToolsWebContents.
|
||||
content::WebContents* external_devtools_web_contents_ = nullptr;
|
||||
|
||||
bool is_guest_;
|
||||
std::unique_ptr<InspectableWebContentsView> view_;
|
||||
|
||||
class NetworkResourceLoader;
|
||||
std::set<std::unique_ptr<NetworkResourceLoader>, base::UniquePtrComparator>
|
||||
loaders_;
|
||||
|
||||
using ExtensionsAPIs = std::map<std::string, std::string>;
|
||||
ExtensionsAPIs extensions_api_;
|
||||
|
||||
base::WeakPtrFactory<InspectableWebContentsImpl> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsImpl);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_IMPL_H_
|
62
shell/browser/ui/inspectable_web_contents_view.h
Normal file
62
shell/browser/ui/inspectable_web_contents_view.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
||||
|
||||
#include "base/strings/string16.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
class DevToolsContentsResizingStrategy;
|
||||
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
namespace views {
|
||||
class View;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsViewDelegate;
|
||||
|
||||
class InspectableWebContentsView {
|
||||
public:
|
||||
InspectableWebContentsView() : delegate_(nullptr) {}
|
||||
virtual ~InspectableWebContentsView() {}
|
||||
|
||||
// The delegate manages its own life.
|
||||
void SetDelegate(InspectableWebContentsViewDelegate* delegate) {
|
||||
delegate_ = delegate;
|
||||
}
|
||||
InspectableWebContentsViewDelegate* GetDelegate() const { return delegate_; }
|
||||
|
||||
#if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX)
|
||||
// Returns the container control, which has devtools view attached.
|
||||
virtual views::View* GetView() = 0;
|
||||
|
||||
// Returns the web view control, which can be used by the
|
||||
// GetInitiallyFocusedView() to set initial focus to web view.
|
||||
virtual views::View* GetWebView() = 0;
|
||||
#else
|
||||
virtual gfx::NativeView GetNativeView() const = 0;
|
||||
#endif
|
||||
|
||||
virtual void ShowDevTools(bool activate) = 0;
|
||||
// Hide the DevTools view.
|
||||
virtual void CloseDevTools() = 0;
|
||||
virtual bool IsDevToolsViewShowing() = 0;
|
||||
virtual bool IsDevToolsViewFocused() = 0;
|
||||
virtual void SetIsDocked(bool docked, bool activate) = 0;
|
||||
virtual void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) = 0;
|
||||
virtual void SetTitle(const base::string16& title) = 0;
|
||||
|
||||
private:
|
||||
InspectableWebContentsViewDelegate* delegate_; // weak references.
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_H_
|
14
shell/browser/ui/inspectable_web_contents_view_delegate.cc
Normal file
14
shell/browser/ui/inspectable_web_contents_view_delegate.cc
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
gfx::ImageSkia InspectableWebContentsViewDelegate::GetDevToolsWindowIcon() {
|
||||
return gfx::ImageSkia();
|
||||
}
|
||||
|
||||
} // namespace atom
|
35
shell/browser/ui/inspectable_web_contents_view_delegate.h
Normal file
35
shell/browser/ui/inspectable_web_contents_view_delegate.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsViewDelegate {
|
||||
public:
|
||||
virtual ~InspectableWebContentsViewDelegate() {}
|
||||
|
||||
virtual void DevToolsFocused() {}
|
||||
virtual void DevToolsOpened() {}
|
||||
virtual void DevToolsClosed() {}
|
||||
|
||||
// Returns the icon of devtools window.
|
||||
virtual gfx::ImageSkia GetDevToolsWindowIcon();
|
||||
|
||||
#if defined(USE_X11)
|
||||
// Called when creating devtools window.
|
||||
virtual void GetDevToolsWindowWMClass(std::string* name,
|
||||
std::string* class_name) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_DELEGATE_H_
|
50
shell/browser/ui/inspectable_web_contents_view_mac.h
Normal file
50
shell/browser/ui/inspectable_web_contents_view_mac.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
||||
#define ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_view.h"
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
|
||||
@class AtomInspectableWebContentsView;
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsImpl;
|
||||
|
||||
class InspectableWebContentsViewMac : public InspectableWebContentsView {
|
||||
public:
|
||||
explicit InspectableWebContentsViewMac(
|
||||
InspectableWebContentsImpl* inspectable_web_contents_impl);
|
||||
~InspectableWebContentsViewMac() override;
|
||||
|
||||
gfx::NativeView GetNativeView() const override;
|
||||
void ShowDevTools(bool activate) override;
|
||||
void CloseDevTools() override;
|
||||
bool IsDevToolsViewShowing() override;
|
||||
bool IsDevToolsViewFocused() override;
|
||||
void SetIsDocked(bool docked, bool activate) override;
|
||||
void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) override;
|
||||
void SetTitle(const base::string16& title) override;
|
||||
|
||||
InspectableWebContentsImpl* inspectable_web_contents() {
|
||||
return inspectable_web_contents_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Owns us.
|
||||
InspectableWebContentsImpl* inspectable_web_contents_;
|
||||
|
||||
base::scoped_nsobject<AtomInspectableWebContentsView> view_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsViewMac);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_INSPECTABLE_WEB_CONTENTS_VIEW_MAC_H_
|
66
shell/browser/ui/inspectable_web_contents_view_mac.mm
Normal file
66
shell/browser/ui/inspectable_web_contents_view_mac.mm
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_mac.h"
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_inspectable_web_contents_view.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContentsImpl* inspectable_web_contents) {
|
||||
return new InspectableWebContentsViewMac(inspectable_web_contents);
|
||||
}
|
||||
|
||||
InspectableWebContentsViewMac::InspectableWebContentsViewMac(
|
||||
InspectableWebContentsImpl* inspectable_web_contents)
|
||||
: inspectable_web_contents_(inspectable_web_contents),
|
||||
view_([[AtomInspectableWebContentsView alloc]
|
||||
initWithInspectableWebContentsViewMac:this]) {}
|
||||
|
||||
InspectableWebContentsViewMac::~InspectableWebContentsViewMac() {
|
||||
[view_ removeObservers];
|
||||
CloseDevTools();
|
||||
}
|
||||
|
||||
gfx::NativeView InspectableWebContentsViewMac::GetNativeView() const {
|
||||
return view_.get();
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::ShowDevTools(bool activate) {
|
||||
[view_ setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::CloseDevTools() {
|
||||
[view_ setDevToolsVisible:NO activate:NO];
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewMac::IsDevToolsViewShowing() {
|
||||
return [view_ isDevToolsVisible];
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewMac::IsDevToolsViewFocused() {
|
||||
return [view_ isDevToolsFocused];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetIsDocked(bool docked, bool activate) {
|
||||
[view_ setIsDocked:docked activate:activate];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) {
|
||||
[view_ setContentsResizingStrategy:strategy];
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewMac::SetTitle(const base::string16& title) {
|
||||
[view_ setTitle:base::SysUTF16ToNSString(title)];
|
||||
}
|
||||
|
||||
} // namespace atom
|
65
shell/browser/ui/message_box.h
Normal file
65
shell/browser/ui/message_box.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_MESSAGE_BOX_H_
|
||||
#define ATOM_BROWSER_UI_MESSAGE_BOX_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/strings/string16.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
enum class MessageBoxType {
|
||||
kNone = 0,
|
||||
kInformation,
|
||||
kWarning,
|
||||
kError,
|
||||
kQuestion,
|
||||
};
|
||||
|
||||
enum MessageBoxOptions {
|
||||
MESSAGE_BOX_NONE = 0,
|
||||
MESSAGE_BOX_NO_LINK = 1 << 0,
|
||||
};
|
||||
|
||||
struct MessageBoxSettings {
|
||||
atom::NativeWindow* parent_window = nullptr;
|
||||
MessageBoxType type = atom::MessageBoxType::kNone;
|
||||
std::vector<std::string> buttons;
|
||||
int default_id;
|
||||
int cancel_id;
|
||||
int options = atom::MessageBoxOptions::MESSAGE_BOX_NONE;
|
||||
std::string title;
|
||||
std::string message;
|
||||
std::string detail;
|
||||
std::string checkbox_label;
|
||||
bool checkbox_checked = false;
|
||||
gfx::ImageSkia icon;
|
||||
|
||||
MessageBoxSettings();
|
||||
MessageBoxSettings(const MessageBoxSettings&);
|
||||
~MessageBoxSettings();
|
||||
};
|
||||
|
||||
int ShowMessageBoxSync(const MessageBoxSettings& settings);
|
||||
|
||||
typedef base::OnceCallback<void(int code, bool checkbox_checked)>
|
||||
MessageBoxCallback;
|
||||
|
||||
void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback);
|
||||
|
||||
// Like ShowMessageBox with simplest settings, but safe to call at very early
|
||||
// stage of application.
|
||||
void ShowErrorBox(const base::string16& title, const base::string16& content);
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_MESSAGE_BOX_H_
|
227
shell/browser/ui/message_box_gtk.cc
Normal file
227
shell/browser/ui/message_box_gtk.cc
Normal file
|
@ -0,0 +1,227 @@
|
|||
// Copyright (c) 2015 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/message_box.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/browser/native_window_observer.h"
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/unresponsive_suppressor.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/browser/ui/libgtkui/gtk_util.h"
|
||||
#include "chrome/browser/ui/libgtkui/skia_utils_gtk.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
|
||||
|
||||
#define ANSI_FOREGROUND_RED "\x1b[31m"
|
||||
#define ANSI_FOREGROUND_BLACK "\x1b[30m"
|
||||
#define ANSI_TEXT_BOLD "\x1b[1m"
|
||||
#define ANSI_BACKGROUND_GRAY "\x1b[47m"
|
||||
#define ANSI_RESET "\x1b[0m"
|
||||
|
||||
namespace atom {
|
||||
|
||||
MessageBoxSettings::MessageBoxSettings() = default;
|
||||
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
|
||||
MessageBoxSettings::~MessageBoxSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
class GtkMessageBox : public NativeWindowObserver {
|
||||
public:
|
||||
explicit GtkMessageBox(const MessageBoxSettings& settings)
|
||||
: cancel_id_(settings.cancel_id),
|
||||
parent_(static_cast<NativeWindow*>(settings.parent_window)) {
|
||||
// Create dialog.
|
||||
dialog_ =
|
||||
gtk_message_dialog_new(nullptr, // parent
|
||||
static_cast<GtkDialogFlags>(0), // no flags
|
||||
GetMessageType(settings.type), // type
|
||||
GTK_BUTTONS_NONE, // no buttons
|
||||
"%s", settings.message.c_str());
|
||||
if (!settings.detail.empty())
|
||||
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog_),
|
||||
"%s", settings.detail.c_str());
|
||||
if (!settings.title.empty())
|
||||
gtk_window_set_title(GTK_WINDOW(dialog_), settings.title.c_str());
|
||||
|
||||
if (!settings.icon.isNull()) {
|
||||
// No easy way to obtain this programmatically, but GTK+'s docs
|
||||
// define GTK_ICON_SIZE_DIALOG to be 48 pixels
|
||||
static constexpr int pixel_width = 48;
|
||||
static constexpr int pixel_height = 48;
|
||||
GdkPixbuf* pixbuf =
|
||||
libgtkui::GdkPixbufFromSkBitmap(*settings.icon.bitmap());
|
||||
GdkPixbuf* scaled_pixbuf = gdk_pixbuf_scale_simple(
|
||||
pixbuf, pixel_width, pixel_height, GDK_INTERP_BILINEAR);
|
||||
GtkWidget* w = gtk_image_new_from_pixbuf(scaled_pixbuf);
|
||||
gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), w);
|
||||
gtk_widget_show(w);
|
||||
g_clear_pointer(&scaled_pixbuf, g_object_unref);
|
||||
g_clear_pointer(&pixbuf, g_object_unref);
|
||||
}
|
||||
|
||||
if (!settings.checkbox_label.empty()) {
|
||||
GtkWidget* message_area =
|
||||
gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog_));
|
||||
GtkWidget* check_button =
|
||||
gtk_check_button_new_with_label(settings.checkbox_label.c_str());
|
||||
g_signal_connect(check_button, "toggled",
|
||||
G_CALLBACK(OnCheckboxToggledThunk), this);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
|
||||
settings.checkbox_checked);
|
||||
gtk_container_add(GTK_CONTAINER(message_area), check_button);
|
||||
gtk_widget_show(check_button);
|
||||
}
|
||||
|
||||
// Add buttons.
|
||||
GtkDialog* dialog = GTK_DIALOG(dialog_);
|
||||
for (size_t i = 0; i < settings.buttons.size(); ++i) {
|
||||
gtk_dialog_add_button(dialog, TranslateToStock(i, settings.buttons[i]),
|
||||
i);
|
||||
}
|
||||
gtk_dialog_set_default_response(dialog, settings.default_id);
|
||||
|
||||
// Parent window.
|
||||
if (parent_) {
|
||||
parent_->AddObserver(this);
|
||||
static_cast<NativeWindowViews*>(parent_)->SetEnabled(false);
|
||||
libgtkui::SetGtkTransientForAura(dialog_, parent_->GetNativeWindow());
|
||||
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
~GtkMessageBox() override {
|
||||
gtk_widget_destroy(dialog_);
|
||||
if (parent_) {
|
||||
parent_->RemoveObserver(this);
|
||||
static_cast<NativeWindowViews*>(parent_)->SetEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
GtkMessageType GetMessageType(MessageBoxType type) {
|
||||
switch (type) {
|
||||
case MessageBoxType::kInformation:
|
||||
return GTK_MESSAGE_INFO;
|
||||
case MessageBoxType::kWarning:
|
||||
return GTK_MESSAGE_WARNING;
|
||||
case MessageBoxType::kQuestion:
|
||||
return GTK_MESSAGE_QUESTION;
|
||||
case MessageBoxType::kError:
|
||||
return GTK_MESSAGE_ERROR;
|
||||
default:
|
||||
return GTK_MESSAGE_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
const char* TranslateToStock(int id, const std::string& text) {
|
||||
const std::string lower = base::ToLowerASCII(text);
|
||||
if (lower == "cancel")
|
||||
return _("_Cancel");
|
||||
if (lower == "no")
|
||||
return _("_No");
|
||||
if (lower == "ok")
|
||||
return _("_OK");
|
||||
if (lower == "yes")
|
||||
return _("_Yes");
|
||||
return text.c_str();
|
||||
}
|
||||
|
||||
void Show() {
|
||||
gtk_widget_show(dialog_);
|
||||
// We need to call gtk_window_present after making the widgets visible to
|
||||
// make sure window gets correctly raised and gets focus.
|
||||
int time = ui::X11EventSource::GetInstance()->GetTimestamp();
|
||||
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
|
||||
}
|
||||
|
||||
int RunSynchronous() {
|
||||
Show();
|
||||
int response = gtk_dialog_run(GTK_DIALOG(dialog_));
|
||||
return (response < 0) ? cancel_id_ : response;
|
||||
}
|
||||
|
||||
void RunAsynchronous(MessageBoxCallback callback) {
|
||||
callback_ = std::move(callback);
|
||||
|
||||
g_signal_connect(dialog_, "delete-event",
|
||||
G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
|
||||
g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseDialogThunk),
|
||||
this);
|
||||
Show();
|
||||
}
|
||||
|
||||
void OnWindowClosed() override {
|
||||
parent_->RemoveObserver(this);
|
||||
parent_ = nullptr;
|
||||
}
|
||||
|
||||
CHROMEG_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, GtkWidget*, int);
|
||||
CHROMEG_CALLBACK_0(GtkMessageBox, void, OnCheckboxToggled, GtkWidget*);
|
||||
|
||||
private:
|
||||
atom::UnresponsiveSuppressor unresponsive_suppressor_;
|
||||
|
||||
// The id to return when the dialog is closed without pressing buttons.
|
||||
int cancel_id_ = 0;
|
||||
|
||||
bool checkbox_checked_ = false;
|
||||
|
||||
NativeWindow* parent_;
|
||||
GtkWidget* dialog_;
|
||||
MessageBoxCallback callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(GtkMessageBox);
|
||||
};
|
||||
|
||||
void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
|
||||
gtk_widget_hide(dialog_);
|
||||
|
||||
if (response < 0)
|
||||
std::move(callback_).Run(cancel_id_, checkbox_checked_);
|
||||
else
|
||||
std::move(callback_).Run(response, checkbox_checked_);
|
||||
delete this;
|
||||
}
|
||||
|
||||
void GtkMessageBox::OnCheckboxToggled(GtkWidget* widget) {
|
||||
checkbox_checked_ = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
|
||||
return GtkMessageBox(settings).RunSynchronous();
|
||||
}
|
||||
|
||||
void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback) {
|
||||
(new GtkMessageBox(settings))->RunAsynchronous(std::move(callback));
|
||||
}
|
||||
|
||||
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||
if (Browser::Get()->is_ready()) {
|
||||
atom::MessageBoxSettings settings;
|
||||
settings.type = atom::MessageBoxType::kError;
|
||||
settings.buttons = {"OK"};
|
||||
settings.title = "Error";
|
||||
settings.message = base::UTF16ToUTF8(title);
|
||||
settings.detail = base::UTF16ToUTF8(content);
|
||||
|
||||
GtkMessageBox(settings).RunSynchronous();
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY ANSI_FOREGROUND_RED
|
||||
"%s\n" ANSI_FOREGROUND_BLACK "%s" ANSI_RESET "\n",
|
||||
base::UTF16ToUTF8(title).c_str(),
|
||||
base::UTF16ToUTF8(content).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace atom
|
152
shell/browser/ui/message_box_mac.mm
Normal file
152
shell/browser/ui/message_box_mac.mm
Normal file
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/message_box.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "skia/ext/skia_utils_mac.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
MessageBoxSettings::MessageBoxSettings() = default;
|
||||
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
|
||||
MessageBoxSettings::~MessageBoxSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
NSAlert* CreateNSAlert(const MessageBoxSettings& settings) {
|
||||
// Ignore the title; it's the window title on other platforms and ignorable.
|
||||
NSAlert* alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:base::SysUTF8ToNSString(settings.message)];
|
||||
[alert setInformativeText:base::SysUTF8ToNSString(settings.detail)];
|
||||
|
||||
switch (settings.type) {
|
||||
case MessageBoxType::kInformation:
|
||||
alert.alertStyle = NSInformationalAlertStyle;
|
||||
break;
|
||||
case MessageBoxType::kWarning:
|
||||
case MessageBoxType::kError:
|
||||
// NSWarningAlertStyle shows the app icon while NSCriticalAlertStyle
|
||||
// shows a warning icon with an app icon badge. Since there is no
|
||||
// error variant, lets just use NSCriticalAlertStyle.
|
||||
alert.alertStyle = NSCriticalAlertStyle;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < settings.buttons.size(); ++i) {
|
||||
NSString* title = base::SysUTF8ToNSString(settings.buttons[i]);
|
||||
// An empty title causes crash on macOS.
|
||||
if (settings.buttons[i].empty())
|
||||
title = @"(empty)";
|
||||
NSButton* button = [alert addButtonWithTitle:title];
|
||||
[button setTag:i];
|
||||
}
|
||||
|
||||
NSArray* ns_buttons = [alert buttons];
|
||||
int button_count = static_cast<int>([ns_buttons count]);
|
||||
|
||||
if (settings.default_id >= 0 && settings.default_id < button_count) {
|
||||
// Focus the button at default_id if the user opted to do so.
|
||||
// The first button added gets set as the default selected.
|
||||
// So remove that default, and make the requested button the default.
|
||||
[[ns_buttons objectAtIndex:0] setKeyEquivalent:@""];
|
||||
[[ns_buttons objectAtIndex:settings.default_id] setKeyEquivalent:@"\r"];
|
||||
}
|
||||
|
||||
// Bind cancel id button to escape key if there is more than one button
|
||||
if (button_count > 1 && settings.cancel_id >= 0 &&
|
||||
settings.cancel_id < button_count) {
|
||||
[[ns_buttons objectAtIndex:settings.cancel_id] setKeyEquivalent:@"\e"];
|
||||
}
|
||||
|
||||
if (!settings.checkbox_label.empty()) {
|
||||
alert.showsSuppressionButton = YES;
|
||||
alert.suppressionButton.title =
|
||||
base::SysUTF8ToNSString(settings.checkbox_label);
|
||||
alert.suppressionButton.state =
|
||||
settings.checkbox_checked ? NSOnState : NSOffState;
|
||||
}
|
||||
|
||||
if (!settings.icon.isNull()) {
|
||||
NSImage* image = skia::SkBitmapToNSImageWithColorSpace(
|
||||
*settings.icon.bitmap(), base::mac::GetGenericRGBColorSpace());
|
||||
[alert setIcon:image];
|
||||
}
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
|
||||
NSAlert* alert = CreateNSAlert(settings);
|
||||
|
||||
// Use runModal for synchronous alert without parent, since we don't have a
|
||||
// window to wait for.
|
||||
if (!settings.parent_window)
|
||||
return [[alert autorelease] runModal];
|
||||
|
||||
__block int ret_code = -1;
|
||||
|
||||
NSWindow* window =
|
||||
settings.parent_window->GetNativeWindow().GetNativeNSWindow();
|
||||
[alert beginSheetModalForWindow:window
|
||||
completionHandler:^(NSModalResponse response) {
|
||||
ret_code = response;
|
||||
[NSApp stopModal];
|
||||
}];
|
||||
|
||||
[NSApp runModalForWindow:window];
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback) {
|
||||
NSAlert* alert = CreateNSAlert(settings);
|
||||
|
||||
// Use runModal for synchronous alert without parent, since we don't have a
|
||||
// window to wait for.
|
||||
if (!settings.parent_window) {
|
||||
int ret = [[alert autorelease] runModal];
|
||||
std::move(callback).Run(ret, alert.suppressionButton.state == NSOnState);
|
||||
} else {
|
||||
NSWindow* window =
|
||||
settings.parent_window
|
||||
? settings.parent_window->GetNativeWindow().GetNativeNSWindow()
|
||||
: nil;
|
||||
|
||||
// Duplicate the callback object here since c is a reference and gcd would
|
||||
// only store the pointer, by duplication we can force gcd to store a copy.
|
||||
__block MessageBoxCallback callback_ = std::move(callback);
|
||||
|
||||
[alert beginSheetModalForWindow:window
|
||||
completionHandler:^(NSModalResponse response) {
|
||||
std::move(callback_).Run(
|
||||
response, alert.suppressionButton.state == NSOnState);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||
NSAlert* alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:base::SysUTF16ToNSString(title)];
|
||||
[alert setInformativeText:base::SysUTF16ToNSString(content)];
|
||||
[alert setAlertStyle:NSCriticalAlertStyle];
|
||||
[alert runModal];
|
||||
[alert release];
|
||||
}
|
||||
|
||||
} // namespace atom
|
249
shell/browser/ui/message_box_win.cc
Normal file
249
shell/browser/ui/message_box_win.cc
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/message_box.h"
|
||||
|
||||
#include <windows.h> // windows.h must be included first
|
||||
|
||||
#include <commctrl.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/unresponsive_suppressor.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "base/win/scoped_gdi_object.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "ui/gfx/icon_util.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
MessageBoxSettings::MessageBoxSettings() = default;
|
||||
MessageBoxSettings::MessageBoxSettings(const MessageBoxSettings&) = default;
|
||||
MessageBoxSettings::~MessageBoxSettings() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
// Small command ID values are already taken by Windows, we have to start from
|
||||
// a large number to avoid conflicts with Windows.
|
||||
const int kIDStart = 100;
|
||||
|
||||
// Get the common ID from button's name.
|
||||
struct CommonButtonID {
|
||||
int button;
|
||||
int id;
|
||||
};
|
||||
CommonButtonID GetCommonID(const base::string16& button) {
|
||||
base::string16 lower = base::ToLowerASCII(button);
|
||||
if (lower == L"ok")
|
||||
return {TDCBF_OK_BUTTON, IDOK};
|
||||
else if (lower == L"yes")
|
||||
return {TDCBF_YES_BUTTON, IDYES};
|
||||
else if (lower == L"no")
|
||||
return {TDCBF_NO_BUTTON, IDNO};
|
||||
else if (lower == L"cancel")
|
||||
return {TDCBF_CANCEL_BUTTON, IDCANCEL};
|
||||
else if (lower == L"retry")
|
||||
return {TDCBF_RETRY_BUTTON, IDRETRY};
|
||||
else if (lower == L"close")
|
||||
return {TDCBF_CLOSE_BUTTON, IDCLOSE};
|
||||
return {-1, -1};
|
||||
}
|
||||
|
||||
// Determine whether the buttons are common buttons, if so map common ID
|
||||
// to button ID.
|
||||
void MapToCommonID(const std::vector<base::string16>& buttons,
|
||||
std::map<int, int>* id_map,
|
||||
TASKDIALOG_COMMON_BUTTON_FLAGS* button_flags,
|
||||
std::vector<TASKDIALOG_BUTTON>* dialog_buttons) {
|
||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||
auto common = GetCommonID(buttons[i]);
|
||||
if (common.button != -1) {
|
||||
// It is a common button.
|
||||
(*id_map)[common.id] = i;
|
||||
(*button_flags) |= common.button;
|
||||
} else {
|
||||
// It is a custom button.
|
||||
dialog_buttons->push_back(
|
||||
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ShowTaskDialogUTF16(NativeWindow* parent,
|
||||
MessageBoxType type,
|
||||
const std::vector<base::string16>& buttons,
|
||||
int default_id,
|
||||
int cancel_id,
|
||||
int options,
|
||||
const base::string16& title,
|
||||
const base::string16& message,
|
||||
const base::string16& detail,
|
||||
const base::string16& checkbox_label,
|
||||
bool* checkbox_checked,
|
||||
const gfx::ImageSkia& icon) {
|
||||
TASKDIALOG_FLAGS flags =
|
||||
TDF_SIZE_TO_CONTENT | // Show all content.
|
||||
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
|
||||
|
||||
TASKDIALOGCONFIG config = {0};
|
||||
config.cbSize = sizeof(config);
|
||||
config.hInstance = GetModuleHandle(NULL);
|
||||
config.dwFlags = flags;
|
||||
|
||||
if (parent) {
|
||||
config.hwndParent =
|
||||
static_cast<atom::NativeWindowViews*>(parent)->GetAcceleratedWidget();
|
||||
}
|
||||
|
||||
if (default_id > 0)
|
||||
config.nDefaultButton = kIDStart + default_id;
|
||||
|
||||
// TaskDialogIndirect doesn't allow empty name, if we set empty title it
|
||||
// will show "electron.exe" in title.
|
||||
base::string16 app_name = base::UTF8ToUTF16(Browser::Get()->GetName());
|
||||
if (title.empty())
|
||||
config.pszWindowTitle = app_name.c_str();
|
||||
else
|
||||
config.pszWindowTitle = title.c_str();
|
||||
|
||||
base::win::ScopedHICON hicon;
|
||||
if (!icon.isNull()) {
|
||||
hicon = IconUtil::CreateHICONFromSkBitmap(*icon.bitmap());
|
||||
config.dwFlags |= TDF_USE_HICON_MAIN;
|
||||
config.hMainIcon = hicon.get();
|
||||
} else {
|
||||
// Show icon according to dialog's type.
|
||||
switch (type) {
|
||||
case MessageBoxType::kInformation:
|
||||
case MessageBoxType::kQuestion:
|
||||
config.pszMainIcon = TD_INFORMATION_ICON;
|
||||
break;
|
||||
case MessageBoxType::kWarning:
|
||||
config.pszMainIcon = TD_WARNING_ICON;
|
||||
break;
|
||||
case MessageBoxType::kError:
|
||||
config.pszMainIcon = TD_ERROR_ICON;
|
||||
break;
|
||||
case MessageBoxType::kNone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If "detail" is empty then don't make message hilighted.
|
||||
if (detail.empty()) {
|
||||
config.pszContent = message.c_str();
|
||||
} else {
|
||||
config.pszMainInstruction = message.c_str();
|
||||
config.pszContent = detail.c_str();
|
||||
}
|
||||
|
||||
if (!checkbox_label.empty()) {
|
||||
config.pszVerificationText = checkbox_label.c_str();
|
||||
|
||||
if (checkbox_checked && *checkbox_checked) {
|
||||
config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the buttons, put common buttons in dwCommonButtons
|
||||
// and custom buttons in pButtons.
|
||||
std::map<int, int> id_map;
|
||||
std::vector<TASKDIALOG_BUTTON> dialog_buttons;
|
||||
if (options & MESSAGE_BOX_NO_LINK) {
|
||||
for (size_t i = 0; i < buttons.size(); ++i)
|
||||
dialog_buttons.push_back(
|
||||
{static_cast<int>(i + kIDStart), buttons[i].c_str()});
|
||||
} else {
|
||||
MapToCommonID(buttons, &id_map, &config.dwCommonButtons, &dialog_buttons);
|
||||
}
|
||||
if (dialog_buttons.size() > 0) {
|
||||
config.pButtons = &dialog_buttons.front();
|
||||
config.cButtons = dialog_buttons.size();
|
||||
if (!(options & MESSAGE_BOX_NO_LINK))
|
||||
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
BOOL verificationFlagChecked = FALSE;
|
||||
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
|
||||
if (checkbox_checked) {
|
||||
*checkbox_checked = verificationFlagChecked;
|
||||
}
|
||||
|
||||
if (id_map.find(id) != id_map.end()) // common button.
|
||||
return id_map[id];
|
||||
else if (id >= kIDStart) // custom button.
|
||||
return id - kIDStart;
|
||||
else
|
||||
return cancel_id;
|
||||
}
|
||||
|
||||
int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
||||
std::vector<base::string16> utf16_buttons;
|
||||
for (const auto& button : settings.buttons)
|
||||
utf16_buttons.push_back(base::UTF8ToUTF16(button));
|
||||
|
||||
const base::string16 title_16 = base::UTF8ToUTF16(settings.title);
|
||||
const base::string16 message_16 = base::UTF8ToUTF16(settings.message);
|
||||
const base::string16 detail_16 = base::UTF8ToUTF16(settings.detail);
|
||||
const base::string16 checkbox_label_16 =
|
||||
base::UTF8ToUTF16(settings.checkbox_label);
|
||||
bool cb_checked = settings.checkbox_checked;
|
||||
|
||||
return ShowTaskDialogUTF16(
|
||||
settings.parent_window, settings.type, utf16_buttons, settings.default_id,
|
||||
settings.cancel_id, settings.options, title_16, message_16, detail_16,
|
||||
checkbox_label_16, &cb_checked, settings.icon);
|
||||
}
|
||||
|
||||
void RunMessageBoxInNewThread(base::Thread* thread,
|
||||
const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback) {
|
||||
int result = ShowTaskDialogUTF8(settings);
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {content::BrowserThread::UI},
|
||||
base::BindOnce(std::move(callback), result, settings.checkbox_checked));
|
||||
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
|
||||
thread);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
|
||||
atom::UnresponsiveSuppressor suppressor;
|
||||
return ShowTaskDialogUTF8(settings);
|
||||
}
|
||||
|
||||
void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback) {
|
||||
auto thread =
|
||||
std::make_unique<base::Thread>(ELECTRON_PRODUCT_NAME "MessageBoxThread");
|
||||
thread->init_com_with_mta(false);
|
||||
if (!thread->Start()) {
|
||||
std::move(callback).Run(settings.cancel_id, settings.checkbox_checked);
|
||||
return;
|
||||
}
|
||||
|
||||
base::Thread* unretained = thread.release();
|
||||
unretained->task_runner()->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&RunMessageBoxInNewThread, base::Unretained(unretained),
|
||||
settings, std::move(callback)));
|
||||
}
|
||||
|
||||
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||
atom::UnresponsiveSuppressor suppressor;
|
||||
ShowTaskDialogUTF16(nullptr, MessageBoxType::kError, {}, -1, 0, 0, L"Error",
|
||||
title, content, L"", nullptr, gfx::ImageSkia());
|
||||
}
|
||||
|
||||
} // namespace atom
|
105
shell/browser/ui/tray_icon.cc
Normal file
105
shell/browser/ui/tray_icon.cc
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/tray_icon.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
TrayIcon::TrayIcon() {}
|
||||
|
||||
TrayIcon::~TrayIcon() {}
|
||||
|
||||
void TrayIcon::SetPressedImage(ImageType image) {}
|
||||
|
||||
void TrayIcon::SetHighlightMode(TrayIcon::HighlightMode mode) {}
|
||||
|
||||
void TrayIcon::DisplayBalloon(ImageType icon,
|
||||
const base::string16& title,
|
||||
const base::string16& contents) {}
|
||||
|
||||
void TrayIcon::PopUpContextMenu(const gfx::Point& pos,
|
||||
AtomMenuModel* menu_model) {}
|
||||
|
||||
gfx::Rect TrayIcon::GetBounds() {
|
||||
return gfx::Rect();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyClicked(const gfx::Rect& bounds,
|
||||
const gfx::Point& location,
|
||||
int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnClicked(bounds, location, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDoubleClicked(const gfx::Rect& bounds, int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDoubleClicked(bounds, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyBalloonShow() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnBalloonShow();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyBalloonClicked() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnBalloonClicked();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyBalloonClosed() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnBalloonClosed();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds, int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnRightClicked(bounds, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDrop() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDrop();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDropFiles(const std::vector<std::string>& files) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDropFiles(files);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDropText(const std::string& text) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDropText(text);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyMouseEntered(const gfx::Point& location, int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnMouseEntered(location, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyMouseExited(const gfx::Point& location, int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnMouseExited(location, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyMouseMoved(const gfx::Point& location, int modifiers) {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnMouseMoved(location, modifiers);
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDragEntered() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDragEntered();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDragExited() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDragExited();
|
||||
}
|
||||
|
||||
void TrayIcon::NotifyDragEnded() {
|
||||
for (TrayIconObserver& observer : observers_)
|
||||
observer.OnDragEnded();
|
||||
}
|
||||
|
||||
} // namespace atom
|
112
shell/browser/ui/tray_icon.h
Normal file
112
shell/browser/ui/tray_icon.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_TRAY_ICON_H_
|
||||
#define ATOM_BROWSER_UI_TRAY_ICON_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "atom/browser/ui/tray_icon_observer.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class TrayIcon {
|
||||
public:
|
||||
static TrayIcon* Create();
|
||||
|
||||
#if defined(OS_WIN)
|
||||
using ImageType = HICON;
|
||||
#else
|
||||
using ImageType = const gfx::Image&;
|
||||
#endif
|
||||
|
||||
virtual ~TrayIcon();
|
||||
|
||||
// Sets the image associated with this status icon.
|
||||
virtual void SetImage(ImageType image) = 0;
|
||||
|
||||
// Sets the image associated with this status icon when pressed.
|
||||
virtual void SetPressedImage(ImageType image);
|
||||
|
||||
// Sets the hover text for this status icon. This is also used as the label
|
||||
// for the menu item which is created as a replacement for the status icon
|
||||
// click action on platforms that do not support custom click actions for the
|
||||
// status icon (e.g. Ubuntu Unity).
|
||||
virtual void SetToolTip(const std::string& tool_tip) = 0;
|
||||
|
||||
// Sets the status icon highlight mode. This only works on macOS.
|
||||
enum class HighlightMode {
|
||||
ALWAYS, // Always highlight the tray icon
|
||||
NEVER, // Never highlight the tray icon
|
||||
SELECTION // Highlight the tray icon when clicked or the menu is opened
|
||||
};
|
||||
virtual void SetHighlightMode(HighlightMode mode);
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
// Set/Get flag determining whether to ignore double click events.
|
||||
virtual void SetIgnoreDoubleClickEvents(bool ignore) = 0;
|
||||
virtual bool GetIgnoreDoubleClickEvents() = 0;
|
||||
|
||||
// Set/Get title displayed next to status icon in the status bar.
|
||||
virtual void SetTitle(const std::string& title) = 0;
|
||||
virtual std::string GetTitle() = 0;
|
||||
#endif
|
||||
|
||||
// Displays a notification balloon with the specified contents.
|
||||
// Depending on the platform it might not appear by the icon tray.
|
||||
virtual void DisplayBalloon(ImageType icon,
|
||||
const base::string16& title,
|
||||
const base::string16& contents);
|
||||
|
||||
// Popups the menu.
|
||||
virtual void PopUpContextMenu(const gfx::Point& pos,
|
||||
AtomMenuModel* menu_model);
|
||||
|
||||
// Set the context menu for this icon.
|
||||
virtual void SetContextMenu(AtomMenuModel* menu_model) = 0;
|
||||
|
||||
// Returns the bounds of tray icon.
|
||||
virtual gfx::Rect GetBounds();
|
||||
|
||||
void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); }
|
||||
void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); }
|
||||
|
||||
void NotifyClicked(const gfx::Rect& = gfx::Rect(),
|
||||
const gfx::Point& location = gfx::Point(),
|
||||
int modifiers = 0);
|
||||
void NotifyDoubleClicked(const gfx::Rect& = gfx::Rect(), int modifiers = 0);
|
||||
void NotifyBalloonShow();
|
||||
void NotifyBalloonClicked();
|
||||
void NotifyBalloonClosed();
|
||||
void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect(),
|
||||
int modifiers = 0);
|
||||
void NotifyDrop();
|
||||
void NotifyDropFiles(const std::vector<std::string>& files);
|
||||
void NotifyDropText(const std::string& text);
|
||||
void NotifyDragEntered();
|
||||
void NotifyDragExited();
|
||||
void NotifyDragEnded();
|
||||
void NotifyMouseEntered(const gfx::Point& location = gfx::Point(),
|
||||
int modifiers = 0);
|
||||
void NotifyMouseExited(const gfx::Point& location = gfx::Point(),
|
||||
int modifiers = 0);
|
||||
void NotifyMouseMoved(const gfx::Point& location = gfx::Point(),
|
||||
int modifiers = 0);
|
||||
|
||||
protected:
|
||||
TrayIcon();
|
||||
|
||||
private:
|
||||
base::ObserverList<TrayIconObserver> observers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TrayIcon);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_TRAY_ICON_H_
|
57
shell/browser/ui/tray_icon_cocoa.h
Normal file
57
shell/browser/ui/tray_icon_cocoa.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
|
||||
#define ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/ui/tray_icon.h"
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
|
||||
@class AtomMenuController;
|
||||
@class StatusItemView;
|
||||
|
||||
namespace atom {
|
||||
|
||||
class TrayIconCocoa : public TrayIcon, public AtomMenuModel::Observer {
|
||||
public:
|
||||
TrayIconCocoa();
|
||||
~TrayIconCocoa() override;
|
||||
|
||||
void SetImage(const gfx::Image& image) override;
|
||||
void SetPressedImage(const gfx::Image& image) override;
|
||||
void SetToolTip(const std::string& tool_tip) override;
|
||||
void SetTitle(const std::string& title) override;
|
||||
std::string GetTitle() override;
|
||||
void SetHighlightMode(TrayIcon::HighlightMode mode) override;
|
||||
void SetIgnoreDoubleClickEvents(bool ignore) override;
|
||||
bool GetIgnoreDoubleClickEvents() override;
|
||||
void PopUpContextMenu(const gfx::Point& pos,
|
||||
AtomMenuModel* menu_model) override;
|
||||
void SetContextMenu(AtomMenuModel* menu_model) override;
|
||||
gfx::Rect GetBounds() override;
|
||||
|
||||
protected:
|
||||
// AtomMenuModel::Observer:
|
||||
void OnMenuWillClose() override;
|
||||
|
||||
private:
|
||||
// Atom custom view for NSStatusItem.
|
||||
base::scoped_nsobject<StatusItemView> status_item_view_;
|
||||
|
||||
// Status menu shown when right-clicking the system icon.
|
||||
base::scoped_nsobject<AtomMenuController> menu_;
|
||||
|
||||
// Used for unregistering observer.
|
||||
AtomMenuModel* menu_model_ = nullptr; // weak ref.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
|
530
shell/browser/ui/tray_icon_cocoa.mm
Normal file
530
shell/browser/ui/tray_icon_cocoa.mm
Normal file
|
@ -0,0 +1,530 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/tray_icon_cocoa.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/browser/mac/atom_application.h"
|
||||
#include "atom/browser/ui/cocoa/NSString+ANSI.h"
|
||||
#include "atom/browser/ui/cocoa/atom_menu_controller.h"
|
||||
#include "base/mac/sdk_forward_declarations.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "ui/display/screen.h"
|
||||
#include "ui/events/cocoa/cocoa_event_utils.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/gfx/mac/coordinate_conversion.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// By default, macOS sets 4px to tray image as left and right padding margin.
|
||||
const CGFloat kHorizontalMargin = 4;
|
||||
// macOS tends to make the title 2px lower.
|
||||
const CGFloat kVerticalTitleMargin = 2;
|
||||
|
||||
} // namespace
|
||||
|
||||
@interface StatusItemView : NSView {
|
||||
atom::TrayIconCocoa* trayIcon_; // weak
|
||||
AtomMenuController* menuController_; // weak
|
||||
atom::TrayIcon::HighlightMode highlight_mode_;
|
||||
BOOL ignoreDoubleClickEvents_;
|
||||
BOOL forceHighlight_;
|
||||
BOOL inMouseEventSequence_;
|
||||
BOOL ANSI_;
|
||||
base::scoped_nsobject<NSImage> image_;
|
||||
base::scoped_nsobject<NSImage> alternateImage_;
|
||||
base::scoped_nsobject<NSString> title_;
|
||||
base::scoped_nsobject<NSMutableAttributedString> attributedTitle_;
|
||||
base::scoped_nsobject<NSStatusItem> statusItem_;
|
||||
base::scoped_nsobject<NSTrackingArea> trackingArea_;
|
||||
}
|
||||
|
||||
@end // @interface StatusItemView
|
||||
|
||||
@implementation StatusItemView
|
||||
|
||||
- (void)dealloc {
|
||||
trayIcon_ = nil;
|
||||
menuController_ = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithIcon:(atom::TrayIconCocoa*)icon {
|
||||
trayIcon_ = icon;
|
||||
menuController_ = nil;
|
||||
highlight_mode_ = atom::TrayIcon::HighlightMode::SELECTION;
|
||||
ignoreDoubleClickEvents_ = NO;
|
||||
forceHighlight_ = NO;
|
||||
inMouseEventSequence_ = NO;
|
||||
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
[self registerForDraggedTypes:@[
|
||||
NSFilenamesPboardType,
|
||||
NSStringPboardType,
|
||||
]];
|
||||
|
||||
// Create the status item.
|
||||
NSStatusItem* item = [[NSStatusBar systemStatusBar]
|
||||
statusItemWithLength:NSVariableStatusItemLength];
|
||||
statusItem_.reset([item retain]);
|
||||
[statusItem_ setView:self];
|
||||
// Finalize setup by sizing our views
|
||||
[self updateDimensions];
|
||||
|
||||
// Add NSTrackingArea for listening to mouseEnter, mouseExit, and mouseMove
|
||||
// events
|
||||
trackingArea_.reset([[NSTrackingArea alloc]
|
||||
initWithRect:[self bounds]
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |
|
||||
NSTrackingActiveAlways
|
||||
owner:self
|
||||
userInfo:nil]);
|
||||
[self addTrackingArea:trackingArea_];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateDimensions {
|
||||
NSStatusBar* bar = [NSStatusBar systemStatusBar];
|
||||
[self setFrame:NSMakeRect(0, 0, [self fullWidth], [bar thickness])];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)removeItem {
|
||||
// Turn off tracking events to prevent crash
|
||||
if (trackingArea_) {
|
||||
[self removeTrackingArea:trackingArea_];
|
||||
trackingArea_.reset();
|
||||
}
|
||||
[[NSStatusBar systemStatusBar] removeStatusItem:statusItem_];
|
||||
[statusItem_ setView:nil];
|
||||
statusItem_.reset();
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect {
|
||||
// Draw the tray icon and title that align with NSStatusItem, layout:
|
||||
// ----------------
|
||||
// | icon | title |
|
||||
/// ----------------
|
||||
|
||||
CGFloat thickness = [[statusItem_ statusBar] thickness];
|
||||
|
||||
// Draw the system bar background.
|
||||
[statusItem_ drawStatusBarBackgroundInRect:self.bounds
|
||||
withHighlight:[self shouldHighlight]];
|
||||
|
||||
// Determine which image to use.
|
||||
NSImage* image = image_.get();
|
||||
if (inMouseEventSequence_ && alternateImage_) {
|
||||
image = alternateImage_.get();
|
||||
}
|
||||
// Apply the higlight color if the image is a template image. When this moves
|
||||
// to using the new [NSStatusItem button] API, this should work automagically.
|
||||
if ([image isTemplate] == YES) {
|
||||
NSImage* imageWithColor = [[image copy] autorelease];
|
||||
[imageWithColor lockFocus];
|
||||
[[self colorWithHighlight:[self isHighlighted]] set];
|
||||
CGRect imageBounds = CGRectMake(0, 0, image.size.width, image.size.height);
|
||||
NSRectFillUsingOperation(imageBounds, NSCompositeSourceAtop);
|
||||
[imageWithColor unlockFocus];
|
||||
image = imageWithColor;
|
||||
}
|
||||
|
||||
// Draw the image
|
||||
[image
|
||||
drawInRect:CGRectMake(roundf(([self iconWidth] - image.size.width) / 2),
|
||||
roundf((thickness - image.size.height) / 2),
|
||||
image.size.width, image.size.height)];
|
||||
|
||||
if (title_) {
|
||||
// Draw title.
|
||||
NSRect titleDrawRect = NSMakeRect([self iconWidth], -kVerticalTitleMargin,
|
||||
[self titleWidth], thickness);
|
||||
[attributedTitle_ drawInRect:titleDrawRect];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDarkMode {
|
||||
if (@available(macOS 10.14, *)) {
|
||||
return [[NSApplication sharedApplication].effectiveAppearance.name
|
||||
isEqualToString:NSAppearanceNameDarkAqua];
|
||||
}
|
||||
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
|
||||
NSString* mode = [defaults stringForKey:@"AppleInterfaceStyle"];
|
||||
return mode && [mode isEqualToString:@"Dark"];
|
||||
}
|
||||
|
||||
- (BOOL)isHighlighted {
|
||||
BOOL highlight = [self shouldHighlight];
|
||||
return highlight | [self isDarkMode];
|
||||
}
|
||||
|
||||
// The width of the full status item.
|
||||
- (CGFloat)fullWidth {
|
||||
if (title_)
|
||||
return [self iconWidth] + [self titleWidth] + kHorizontalMargin;
|
||||
else
|
||||
return [self iconWidth];
|
||||
}
|
||||
|
||||
// The width of the icon.
|
||||
- (CGFloat)iconWidth {
|
||||
if (!image_ && title_)
|
||||
return kHorizontalMargin;
|
||||
CGFloat thickness = [[NSStatusBar systemStatusBar] thickness];
|
||||
CGFloat imageHeight = [image_ size].height;
|
||||
CGFloat imageWidth = [image_ size].width;
|
||||
CGFloat iconWidth = imageWidth;
|
||||
if (imageWidth < thickness) {
|
||||
// Image's width must be larger than menu bar's height.
|
||||
iconWidth = thickness;
|
||||
} else {
|
||||
CGFloat verticalMargin = thickness - imageHeight;
|
||||
// Image must have same horizontal vertical margin.
|
||||
if (verticalMargin > 0 && imageWidth != imageHeight)
|
||||
iconWidth = imageWidth + verticalMargin;
|
||||
CGFloat horizontalMargin = thickness - imageWidth;
|
||||
// Image must have at least kHorizontalMargin horizontal margin on each
|
||||
// side.
|
||||
if (horizontalMargin < 2 * kHorizontalMargin)
|
||||
iconWidth = imageWidth + 2 * kHorizontalMargin;
|
||||
}
|
||||
return iconWidth;
|
||||
}
|
||||
|
||||
// The width of the title.
|
||||
- (CGFloat)titleWidth {
|
||||
if (!title_)
|
||||
return 0;
|
||||
return [attributedTitle_ size].width;
|
||||
}
|
||||
|
||||
- (NSColor*)colorWithHighlight:(BOOL)highlight {
|
||||
return highlight ? [NSColor whiteColor]
|
||||
: [NSColor colorWithRed:0.265625
|
||||
green:0.25390625
|
||||
blue:0.234375
|
||||
alpha:1.0];
|
||||
}
|
||||
|
||||
- (void)setImage:(NSImage*)image {
|
||||
image_.reset([image copy]);
|
||||
[self updateDimensions];
|
||||
}
|
||||
|
||||
- (void)setAlternateImage:(NSImage*)image {
|
||||
alternateImage_.reset([image copy]);
|
||||
}
|
||||
|
||||
- (void)setHighlight:(atom::TrayIcon::HighlightMode)mode {
|
||||
highlight_mode_ = mode;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setIgnoreDoubleClickEvents:(BOOL)ignore {
|
||||
ignoreDoubleClickEvents_ = ignore;
|
||||
}
|
||||
|
||||
- (BOOL)getIgnoreDoubleClickEvents {
|
||||
return ignoreDoubleClickEvents_;
|
||||
}
|
||||
|
||||
- (void)setTitle:(NSString*)title {
|
||||
if (title.length > 0) {
|
||||
title_.reset([title copy]);
|
||||
ANSI_ = [title containsANSICodes];
|
||||
} else {
|
||||
title_.reset();
|
||||
ANSI_ = NO;
|
||||
}
|
||||
[self updateAttributedTitle];
|
||||
[self updateDimensions];
|
||||
}
|
||||
|
||||
- (NSString*)title {
|
||||
return title_;
|
||||
}
|
||||
|
||||
- (void)updateAttributedTitle {
|
||||
NSDictionary* attributes =
|
||||
@{NSFontAttributeName : [NSFont menuBarFontOfSize:0]};
|
||||
|
||||
if (ANSI_) {
|
||||
NSCharacterSet* whites = [NSCharacterSet whitespaceCharacterSet];
|
||||
NSString* title = [title_ stringByTrimmingCharactersInSet:whites];
|
||||
attributedTitle_.reset([title attributedStringParsingANSICodes]);
|
||||
[attributedTitle_ addAttributes:attributes
|
||||
range:NSMakeRange(0, [attributedTitle_ length])];
|
||||
return;
|
||||
}
|
||||
|
||||
// check title_ being nil
|
||||
NSString* title = @"";
|
||||
if (title_)
|
||||
title = title_;
|
||||
|
||||
attributedTitle_.reset([[NSMutableAttributedString alloc]
|
||||
initWithString:title
|
||||
attributes:attributes]);
|
||||
|
||||
// NSFontAttributeName:[NSFont menuBarFontOfSize:0],
|
||||
// NSForegroundColorAttributeName:[self colorWithHighlight: highlight]
|
||||
[attributedTitle_ addAttributes:attributes
|
||||
range:NSMakeRange(0, [attributedTitle_ length])];
|
||||
[attributedTitle_ addAttribute:NSForegroundColorAttributeName
|
||||
value:[self colorWithHighlight:[self isHighlighted]]
|
||||
range:NSMakeRange(0, [attributedTitle_ length])];
|
||||
}
|
||||
|
||||
- (void)setMenuController:(AtomMenuController*)menu {
|
||||
menuController_ = menu;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent*)event {
|
||||
inMouseEventSequence_ = YES;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent*)event {
|
||||
if (!inMouseEventSequence_) {
|
||||
// If the menu is showing, when user clicked the tray icon, the `mouseDown`
|
||||
// event will be dissmissed, we need to close the menu at this time.
|
||||
[self setNeedsDisplay:YES];
|
||||
return;
|
||||
}
|
||||
inMouseEventSequence_ = NO;
|
||||
|
||||
// Show menu when there is a context menu.
|
||||
// NB(hokein): Make tray's behavior more like official one's.
|
||||
// When the tray icon gets clicked quickly multiple times, the
|
||||
// event.clickCount doesn't always return 1. Instead, it returns a value that
|
||||
// counts the clicked times.
|
||||
// So we don't check the clickCount here, just pop up the menu for each click
|
||||
// event.
|
||||
if (menuController_)
|
||||
[statusItem_ popUpStatusItemMenu:[menuController_ menu]];
|
||||
|
||||
// Don't emit click events when menu is showing.
|
||||
if (menuController_)
|
||||
return;
|
||||
|
||||
// If we are ignoring double click events, we should ignore the `clickCount`
|
||||
// value and immediately emit a click event.
|
||||
BOOL shouldBeHandledAsASingleClick =
|
||||
(event.clickCount == 1) || ignoreDoubleClickEvents_;
|
||||
if (shouldBeHandledAsASingleClick)
|
||||
trayIcon_->NotifyClicked(
|
||||
gfx::ScreenRectFromNSRect(event.window.frame),
|
||||
gfx::ScreenPointFromNSPoint([event locationInWindow]),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
|
||||
// Double click event.
|
||||
BOOL shouldBeHandledAsADoubleClick =
|
||||
(event.clickCount == 2) && !ignoreDoubleClickEvents_;
|
||||
if (shouldBeHandledAsADoubleClick)
|
||||
trayIcon_->NotifyDoubleClicked(
|
||||
gfx::ScreenRectFromNSRect(event.window.frame),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)popUpContextMenu:(atom::AtomMenuModel*)menu_model {
|
||||
// Show a custom menu.
|
||||
if (menu_model) {
|
||||
base::scoped_nsobject<AtomMenuController> menuController(
|
||||
[[AtomMenuController alloc] initWithModel:menu_model
|
||||
useDefaultAccelerator:NO]);
|
||||
forceHighlight_ = YES; // Should highlight when showing menu.
|
||||
[self setNeedsDisplay:YES];
|
||||
[statusItem_ popUpStatusItemMenu:[menuController menu]];
|
||||
forceHighlight_ = NO;
|
||||
[self setNeedsDisplay:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
if (menuController_ && ![menuController_ isMenuOpen]) {
|
||||
// Redraw the tray icon to show highlight if it is enabled.
|
||||
[self setNeedsDisplay:YES];
|
||||
[statusItem_ popUpStatusItemMenu:[menuController_ menu]];
|
||||
// The popUpStatusItemMenu returns only after the showing menu is closed.
|
||||
// When it returns, we need to redraw the tray icon to not show highlight.
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rightMouseUp:(NSEvent*)event {
|
||||
trayIcon_->NotifyRightClicked(
|
||||
gfx::ScreenRectFromNSRect(event.window.frame),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
|
||||
trayIcon_->NotifyDragEntered();
|
||||
return NSDragOperationCopy;
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent*)event {
|
||||
trayIcon_->NotifyMouseExited(
|
||||
gfx::ScreenPointFromNSPoint([event locationInWindow]),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent*)event {
|
||||
trayIcon_->NotifyMouseEntered(
|
||||
gfx::ScreenPointFromNSPoint([event locationInWindow]),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (void)mouseMoved:(NSEvent*)event {
|
||||
trayIcon_->NotifyMouseMoved(
|
||||
gfx::ScreenPointFromNSPoint([event locationInWindow]),
|
||||
ui::EventFlagsFromModifiers([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (void)draggingExited:(id<NSDraggingInfo>)sender {
|
||||
trayIcon_->NotifyDragExited();
|
||||
}
|
||||
|
||||
- (void)draggingEnded:(id<NSDraggingInfo>)sender {
|
||||
trayIcon_->NotifyDragEnded();
|
||||
|
||||
if (NSPointInRect([sender draggingLocation], self.frame)) {
|
||||
trayIcon_->NotifyDrop();
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)handleDrop:(id<NSDraggingInfo>)sender {
|
||||
NSPasteboard* pboard = [sender draggingPasteboard];
|
||||
|
||||
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||
std::vector<std::string> dropFiles;
|
||||
NSArray* files = [pboard propertyListForType:NSFilenamesPboardType];
|
||||
for (NSString* file in files)
|
||||
dropFiles.push_back(base::SysNSStringToUTF8(file));
|
||||
trayIcon_->NotifyDropFiles(dropFiles);
|
||||
return YES;
|
||||
} else if ([[pboard types] containsObject:NSStringPboardType]) {
|
||||
NSString* dropText = [pboard stringForType:NSStringPboardType];
|
||||
trayIcon_->NotifyDropText(base::SysNSStringToUTF8(dropText));
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
|
||||
[self handleDrop:sender];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay:(BOOL)display {
|
||||
[self updateAttributedTitle];
|
||||
[super setNeedsDisplay:display];
|
||||
}
|
||||
|
||||
- (BOOL)shouldHighlight {
|
||||
using HighlightMode = atom::TrayIcon::HighlightMode;
|
||||
switch (highlight_mode_) {
|
||||
case HighlightMode::ALWAYS:
|
||||
return true;
|
||||
case HighlightMode::NEVER:
|
||||
return false;
|
||||
case HighlightMode::SELECTION:
|
||||
BOOL isMenuOpen = menuController_ && [menuController_ isMenuOpen];
|
||||
return forceHighlight_ || inMouseEventSequence_ || isMenuOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace atom {
|
||||
|
||||
TrayIconCocoa::TrayIconCocoa() {
|
||||
status_item_view_.reset([[StatusItemView alloc] initWithIcon:this]);
|
||||
}
|
||||
|
||||
TrayIconCocoa::~TrayIconCocoa() {
|
||||
[status_item_view_ removeItem];
|
||||
if (menu_model_)
|
||||
menu_model_->RemoveObserver(this);
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetImage(const gfx::Image& image) {
|
||||
[status_item_view_ setImage:image.IsEmpty() ? nil : image.AsNSImage()];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetPressedImage(const gfx::Image& image) {
|
||||
[status_item_view_ setAlternateImage:image.AsNSImage()];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetToolTip(const std::string& tool_tip) {
|
||||
[status_item_view_ setToolTip:base::SysUTF8ToNSString(tool_tip)];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetTitle(const std::string& title) {
|
||||
[status_item_view_ setTitle:base::SysUTF8ToNSString(title)];
|
||||
}
|
||||
|
||||
std::string TrayIconCocoa::GetTitle() {
|
||||
return base::SysNSStringToUTF8([status_item_view_ title]);
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetHighlightMode(TrayIcon::HighlightMode mode) {
|
||||
[status_item_view_ setHighlight:mode];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetIgnoreDoubleClickEvents(bool ignore) {
|
||||
[status_item_view_ setIgnoreDoubleClickEvents:ignore];
|
||||
}
|
||||
|
||||
bool TrayIconCocoa::GetIgnoreDoubleClickEvents() {
|
||||
return [status_item_view_ getIgnoreDoubleClickEvents];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::PopUpContextMenu(const gfx::Point& pos,
|
||||
AtomMenuModel* menu_model) {
|
||||
[status_item_view_ popUpContextMenu:menu_model];
|
||||
}
|
||||
|
||||
void TrayIconCocoa::SetContextMenu(AtomMenuModel* menu_model) {
|
||||
// Substribe to MenuClosed event.
|
||||
if (menu_model_)
|
||||
menu_model_->RemoveObserver(this);
|
||||
|
||||
menu_model_ = menu_model;
|
||||
|
||||
if (menu_model) {
|
||||
menu_model->AddObserver(this);
|
||||
// Create native menu.
|
||||
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model
|
||||
useDefaultAccelerator:NO]);
|
||||
} else {
|
||||
menu_.reset();
|
||||
}
|
||||
|
||||
[status_item_view_ setMenuController:menu_.get()];
|
||||
}
|
||||
|
||||
gfx::Rect TrayIconCocoa::GetBounds() {
|
||||
auto bounds = gfx::ScreenRectFromNSRect([status_item_view_ window].frame);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
void TrayIconCocoa::OnMenuWillClose() {
|
||||
[status_item_view_ setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create() {
|
||||
return new TrayIconCocoa;
|
||||
}
|
||||
|
||||
} // namespace atom
|
53
shell/browser/ui/tray_icon_gtk.cc
Normal file
53
shell/browser/ui/tray_icon_gtk.cc
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/tray_icon_gtk.h"
|
||||
|
||||
#include "atom/browser/browser.h"
|
||||
#include "atom/common/application_info.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/views/linux_ui/linux_ui.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
TrayIconGtk::TrayIconGtk() {}
|
||||
|
||||
TrayIconGtk::~TrayIconGtk() {}
|
||||
|
||||
void TrayIconGtk::SetImage(const gfx::Image& image) {
|
||||
if (icon_) {
|
||||
icon_->SetImage(image.AsImageSkia());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto toolTip = base::UTF8ToUTF16(GetApplicationName());
|
||||
icon_ = views::LinuxUI::instance()->CreateLinuxStatusIcon(
|
||||
image.AsImageSkia(), toolTip, Browser::Get()->GetName().c_str());
|
||||
icon_->set_delegate(this);
|
||||
}
|
||||
|
||||
void TrayIconGtk::SetToolTip(const std::string& tool_tip) {
|
||||
icon_->SetToolTip(base::UTF8ToUTF16(tool_tip));
|
||||
}
|
||||
|
||||
void TrayIconGtk::SetContextMenu(AtomMenuModel* menu_model) {
|
||||
icon_->UpdatePlatformContextMenu(menu_model);
|
||||
}
|
||||
|
||||
void TrayIconGtk::OnClick() {
|
||||
NotifyClicked();
|
||||
}
|
||||
|
||||
bool TrayIconGtk::HasClickAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create() {
|
||||
return new TrayIconGtk;
|
||||
}
|
||||
|
||||
} // namespace atom
|
42
shell/browser/ui/tray_icon_gtk.h
Normal file
42
shell/browser/ui/tray_icon_gtk.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_TRAY_ICON_GTK_H_
|
||||
#define ATOM_BROWSER_UI_TRAY_ICON_GTK_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/ui/tray_icon.h"
|
||||
#include "ui/views/linux_ui/status_icon_linux.h"
|
||||
|
||||
namespace views {
|
||||
class StatusIconLinux;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class TrayIconGtk : public TrayIcon, public views::StatusIconLinux::Delegate {
|
||||
public:
|
||||
TrayIconGtk();
|
||||
~TrayIconGtk() override;
|
||||
|
||||
// TrayIcon:
|
||||
void SetImage(const gfx::Image& image) override;
|
||||
void SetToolTip(const std::string& tool_tip) override;
|
||||
void SetContextMenu(AtomMenuModel* menu_model) override;
|
||||
|
||||
private:
|
||||
// views::StatusIconLinux::Delegate:
|
||||
void OnClick() override;
|
||||
bool HasClickAction() override;
|
||||
|
||||
std::unique_ptr<views::StatusIconLinux> icon_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TrayIconGtk);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_TRAY_ICON_GTK_H_
|
46
shell/browser/ui/tray_icon_observer.h
Normal file
46
shell/browser/ui/tray_icon_observer.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
|
||||
#define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/observer_list_types.h"
|
||||
|
||||
namespace gfx {
|
||||
class Rect;
|
||||
class Point;
|
||||
} // namespace gfx
|
||||
|
||||
namespace atom {
|
||||
|
||||
class TrayIconObserver : public base::CheckedObserver {
|
||||
public:
|
||||
virtual void OnClicked(const gfx::Rect& bounds,
|
||||
const gfx::Point& location,
|
||||
int modifiers) {}
|
||||
virtual void OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {}
|
||||
virtual void OnBalloonShow() {}
|
||||
virtual void OnBalloonClicked() {}
|
||||
virtual void OnBalloonClosed() {}
|
||||
virtual void OnRightClicked(const gfx::Rect& bounds, int modifiers) {}
|
||||
virtual void OnDrop() {}
|
||||
virtual void OnDropFiles(const std::vector<std::string>& files) {}
|
||||
virtual void OnDropText(const std::string& text) {}
|
||||
virtual void OnDragEntered() {}
|
||||
virtual void OnDragExited() {}
|
||||
virtual void OnDragEnded() {}
|
||||
virtual void OnMouseEntered(const gfx::Point& location, int modifiers) {}
|
||||
virtual void OnMouseExited(const gfx::Point& location, int modifiers) {}
|
||||
virtual void OnMouseMoved(const gfx::Point& location, int modifiers) {}
|
||||
|
||||
protected:
|
||||
~TrayIconObserver() override {}
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
|
16
shell/browser/ui/tray_icon_win.cc
Normal file
16
shell/browser/ui/tray_icon_win.cc
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/win/notify_icon.h"
|
||||
#include "atom/browser/ui/win/notify_icon_host.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
// static
|
||||
TrayIcon* TrayIcon::Create() {
|
||||
static NotifyIconHost host;
|
||||
return host.CreateNotifyIcon();
|
||||
}
|
||||
|
||||
} // namespace atom
|
115
shell/browser/ui/views/atom_views_delegate.cc
Normal file
115
shell/browser/ui/views/atom_views_delegate.cc
Normal file
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/views/atom_views_delegate.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
|
||||
#include "ui/views/widget/native_widget_aura.h"
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#include "base/environment.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "ui/views/linux_ui/linux_ui.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
bool IsDesktopEnvironmentUnity() {
|
||||
std::unique_ptr<base::Environment> env(base::Environment::Create());
|
||||
base::nix::DesktopEnvironment desktop_env =
|
||||
base::nix::GetDesktopEnvironment(env.get());
|
||||
return desktop_env == base::nix::DESKTOP_ENVIRONMENT_UNITY;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace atom {
|
||||
|
||||
ViewsDelegate::ViewsDelegate() {}
|
||||
|
||||
ViewsDelegate::~ViewsDelegate() {}
|
||||
|
||||
void ViewsDelegate::SaveWindowPlacement(const views::Widget* window,
|
||||
const std::string& window_name,
|
||||
const gfx::Rect& bounds,
|
||||
ui::WindowShowState show_state) {}
|
||||
|
||||
bool ViewsDelegate::GetSavedWindowPlacement(
|
||||
const views::Widget* widget,
|
||||
const std::string& window_name,
|
||||
gfx::Rect* bounds,
|
||||
ui::WindowShowState* show_state) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewsDelegate::NotifyMenuItemFocused(const base::string16& menu_name,
|
||||
const base::string16& menu_item_name,
|
||||
int item_index,
|
||||
int item_count,
|
||||
bool has_submenu) {}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
HICON ViewsDelegate::GetDefaultWindowIcon() const {
|
||||
// Use current exe's icon as default window icon.
|
||||
return LoadIcon(GetModuleHandle(NULL),
|
||||
MAKEINTRESOURCE(1 /* IDR_MAINFRAME */));
|
||||
}
|
||||
|
||||
HICON ViewsDelegate::GetSmallWindowIcon() const {
|
||||
return GetDefaultWindowIcon();
|
||||
}
|
||||
|
||||
bool ViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
||||
gfx::ImageSkia* ViewsDelegate::GetDefaultWindowIcon() const {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
views::NonClientFrameView* ViewsDelegate::CreateDefaultNonClientFrameView(
|
||||
views::Widget* widget) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ViewsDelegate::AddRef() {}
|
||||
|
||||
void ViewsDelegate::ReleaseRef() {}
|
||||
|
||||
void ViewsDelegate::OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) {
|
||||
// If we already have a native_widget, we don't have to try to come
|
||||
// up with one.
|
||||
if (params->native_widget)
|
||||
return;
|
||||
|
||||
if (params->parent && params->type != views::Widget::InitParams::TYPE_MENU &&
|
||||
params->type != views::Widget::InitParams::TYPE_TOOLTIP) {
|
||||
params->native_widget = new views::NativeWidgetAura(delegate);
|
||||
} else {
|
||||
params->native_widget = new views::DesktopNativeWidgetAura(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
bool ViewsDelegate::WindowManagerProvidesTitleBar(bool maximized) {
|
||||
#if defined(OS_LINUX)
|
||||
// On Ubuntu Unity, the system always provides a title bar for maximized
|
||||
// windows.
|
||||
if (!maximized)
|
||||
return false;
|
||||
static bool is_desktop_environment_unity = IsDesktopEnvironmentUnity();
|
||||
return is_desktop_environment_unity;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace atom
|
58
shell/browser/ui/views/atom_views_delegate.h
Normal file
58
shell/browser/ui/views/atom_views_delegate.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "ui/views/views_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class ViewsDelegate : public views::ViewsDelegate {
|
||||
public:
|
||||
ViewsDelegate();
|
||||
~ViewsDelegate() override;
|
||||
|
||||
protected:
|
||||
// views::ViewsDelegate:
|
||||
void SaveWindowPlacement(const views::Widget* window,
|
||||
const std::string& window_name,
|
||||
const gfx::Rect& bounds,
|
||||
ui::WindowShowState show_state) override;
|
||||
bool GetSavedWindowPlacement(const views::Widget* widget,
|
||||
const std::string& window_name,
|
||||
gfx::Rect* bounds,
|
||||
ui::WindowShowState* show_state) const override;
|
||||
void NotifyMenuItemFocused(const base::string16& menu_name,
|
||||
const base::string16& menu_item_name,
|
||||
int item_index,
|
||||
int item_count,
|
||||
bool has_submenu) override;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
HICON GetDefaultWindowIcon() const override;
|
||||
HICON GetSmallWindowIcon() const override;
|
||||
bool IsWindowInMetro(gfx::NativeWindow window) const override;
|
||||
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
||||
gfx::ImageSkia* GetDefaultWindowIcon() const override;
|
||||
#endif
|
||||
views::NonClientFrameView* CreateDefaultNonClientFrameView(
|
||||
views::Widget* widget) override;
|
||||
void AddRef() override;
|
||||
void ReleaseRef() override;
|
||||
void OnBeforeWidgetInit(
|
||||
views::Widget::InitParams* params,
|
||||
views::internal::NativeWidgetDelegate* delegate) override;
|
||||
bool WindowManagerProvidesTitleBar(bool maximized) override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ViewsDelegate);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
|
492
shell/browser/ui/views/autofill_popup_view.cc
Normal file
492
shell/browser/ui/views/autofill_popup_view.cc
Normal file
|
@ -0,0 +1,492 @@
|
|||
// Copyright (c) 2012 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 "atom/browser/ui/views/autofill_popup_view.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "cc/paint/skia_paint_canvas.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "ui/events/keycodes/keyboard_codes.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/text_utils.h"
|
||||
#include "ui/views/border.h"
|
||||
#include "ui/views/focus/focus_manager.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
void AutofillPopupChildView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
|
||||
node_data->role = ax::mojom::Role::kMenuItem;
|
||||
node_data->SetName(suggestion_);
|
||||
}
|
||||
|
||||
AutofillPopupView::AutofillPopupView(AutofillPopup* popup,
|
||||
views::Widget* parent_widget)
|
||||
: popup_(popup), parent_widget_(parent_widget), weak_ptr_factory_(this) {
|
||||
CreateChildViews();
|
||||
SetFocusBehavior(FocusBehavior::ALWAYS);
|
||||
set_drag_controller(this);
|
||||
}
|
||||
|
||||
AutofillPopupView::~AutofillPopupView() {
|
||||
if (popup_) {
|
||||
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
|
||||
host->RemoveKeyPressEventCallback(keypress_callback_);
|
||||
popup_->view_ = nullptr;
|
||||
popup_ = nullptr;
|
||||
}
|
||||
|
||||
RemoveObserver();
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
if (view_proxy_.get()) {
|
||||
view_proxy_->ResetView();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GetWidget()) {
|
||||
GetWidget()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::Show() {
|
||||
bool visible = parent_widget_->IsVisible();
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
visible = visible || view_proxy_;
|
||||
#endif
|
||||
if (!popup_ || !visible || parent_widget_->IsClosed())
|
||||
return;
|
||||
|
||||
const bool initialize_widget = !GetWidget();
|
||||
if (initialize_widget) {
|
||||
parent_widget_->AddObserver(this);
|
||||
|
||||
// The widget is destroyed by the corresponding NativeWidget, so we use
|
||||
// a weak pointer to hold the reference and don't have to worry about
|
||||
// deletion.
|
||||
views::Widget* widget = new views::Widget;
|
||||
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
|
||||
params.delegate = this;
|
||||
params.parent = parent_widget_->GetNativeView();
|
||||
widget->Init(params);
|
||||
|
||||
// No animation for popup appearance (too distracting).
|
||||
widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE);
|
||||
|
||||
show_time_ = base::Time::Now();
|
||||
}
|
||||
|
||||
SetBorder(views::CreateSolidBorder(
|
||||
kPopupBorderThickness,
|
||||
GetNativeTheme()->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_UnfocusedBorderColor)));
|
||||
|
||||
DoUpdateBoundsAndRedrawPopup();
|
||||
GetWidget()->Show();
|
||||
|
||||
if (initialize_widget)
|
||||
views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this);
|
||||
|
||||
keypress_callback_ = base::BindRepeating(
|
||||
&AutofillPopupView::HandleKeyPressEvent, base::Unretained(this));
|
||||
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
|
||||
host->AddKeyPressEventCallback(keypress_callback_);
|
||||
|
||||
NotifyAccessibilityEvent(ax::mojom::Event::kMenuStart, true);
|
||||
}
|
||||
|
||||
void AutofillPopupView::Hide() {
|
||||
if (popup_) {
|
||||
auto* host = popup_->frame_host_->GetRenderViewHost()->GetWidget();
|
||||
host->RemoveKeyPressEventCallback(keypress_callback_);
|
||||
popup_ = nullptr;
|
||||
}
|
||||
|
||||
RemoveObserver();
|
||||
NotifyAccessibilityEvent(ax::mojom::Event::kMenuEnd, true);
|
||||
|
||||
if (GetWidget()) {
|
||||
GetWidget()->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnSuggestionsChanged() {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
CreateChildViews();
|
||||
if (popup_->GetLineCount() == 0) {
|
||||
popup_->Hide();
|
||||
return;
|
||||
}
|
||||
DoUpdateBoundsAndRedrawPopup();
|
||||
}
|
||||
|
||||
void AutofillPopupView::WriteDragDataForView(views::View*,
|
||||
const gfx::Point&,
|
||||
ui::OSExchangeData*) {}
|
||||
|
||||
int AutofillPopupView::GetDragOperationsForView(views::View*,
|
||||
const gfx::Point&) {
|
||||
return ui::DragDropTypes::DRAG_NONE;
|
||||
}
|
||||
|
||||
bool AutofillPopupView::CanStartDragForView(views::View*,
|
||||
const gfx::Point&,
|
||||
const gfx::Point&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnSelectedRowChanged(
|
||||
base::Optional<int> previous_row_selection,
|
||||
base::Optional<int> current_row_selection) {
|
||||
SchedulePaint();
|
||||
|
||||
if (current_row_selection) {
|
||||
int selected = current_row_selection.value_or(-1);
|
||||
if (selected == -1 || static_cast<size_t>(selected) >= children().size())
|
||||
return;
|
||||
children().at(selected)->NotifyAccessibilityEvent(
|
||||
ax::mojom::Event::kSelection, true);
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::DrawAutofillEntry(gfx::Canvas* canvas,
|
||||
int index,
|
||||
const gfx::Rect& entry_rect) {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
canvas->FillRect(entry_rect, GetNativeTheme()->GetSystemColor(
|
||||
popup_->GetBackgroundColorIDForRow(index)));
|
||||
|
||||
const bool is_rtl = base::i18n::IsRTL();
|
||||
const int text_align =
|
||||
is_rtl ? gfx::Canvas::TEXT_ALIGN_RIGHT : gfx::Canvas::TEXT_ALIGN_LEFT;
|
||||
gfx::Rect value_rect = entry_rect;
|
||||
value_rect.Inset(kEndPadding, 0);
|
||||
|
||||
int x_align_left = value_rect.x();
|
||||
const int value_width = gfx::GetStringWidth(
|
||||
popup_->GetValueAt(index), popup_->GetValueFontListForRow(index));
|
||||
int value_x_align_left = x_align_left;
|
||||
value_x_align_left =
|
||||
is_rtl ? value_rect.right() - value_width : value_rect.x();
|
||||
|
||||
canvas->DrawStringRectWithFlags(
|
||||
popup_->GetValueAt(index), popup_->GetValueFontListForRow(index),
|
||||
GetNativeTheme()->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_ResultsTableNormalText),
|
||||
gfx::Rect(value_x_align_left, value_rect.y(), value_width,
|
||||
value_rect.height()),
|
||||
text_align);
|
||||
|
||||
// Draw the label text, if one exists.
|
||||
if (!popup_->GetLabelAt(index).empty()) {
|
||||
const int label_width = gfx::GetStringWidth(
|
||||
popup_->GetLabelAt(index), popup_->GetLabelFontListForRow(index));
|
||||
int label_x_align_left = x_align_left;
|
||||
label_x_align_left =
|
||||
is_rtl ? value_rect.x() : value_rect.right() - label_width;
|
||||
|
||||
canvas->DrawStringRectWithFlags(
|
||||
popup_->GetLabelAt(index), popup_->GetLabelFontListForRow(index),
|
||||
GetNativeTheme()->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_ResultsTableDimmedText),
|
||||
gfx::Rect(label_x_align_left, entry_rect.y(), label_width,
|
||||
entry_rect.height()),
|
||||
text_align);
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::CreateChildViews() {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
RemoveAllChildViews(true);
|
||||
|
||||
for (int i = 0; i < popup_->GetLineCount(); ++i) {
|
||||
auto* child_view = new AutofillPopupChildView(popup_->GetValueAt(i));
|
||||
child_view->set_drag_controller(this);
|
||||
AddChildView(child_view);
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::DoUpdateBoundsAndRedrawPopup() {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
GetWidget()->SetBounds(popup_->popup_bounds_);
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
if (view_proxy_.get()) {
|
||||
view_proxy_->SetBounds(popup_->popup_bounds_in_view());
|
||||
}
|
||||
#endif
|
||||
SchedulePaint();
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnPaint(gfx::Canvas* canvas) {
|
||||
if (!popup_ ||
|
||||
static_cast<size_t>(popup_->GetLineCount()) != children().size())
|
||||
return;
|
||||
gfx::Canvas* draw_canvas = canvas;
|
||||
SkBitmap bitmap;
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
std::unique_ptr<cc::SkiaPaintCanvas> paint_canvas;
|
||||
if (view_proxy_.get()) {
|
||||
bitmap.allocN32Pixels(popup_->popup_bounds_in_view().width(),
|
||||
popup_->popup_bounds_in_view().height(), true);
|
||||
paint_canvas.reset(new cc::SkiaPaintCanvas(bitmap));
|
||||
draw_canvas = new gfx::Canvas(paint_canvas.get(), 1.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
draw_canvas->DrawColor(GetNativeTheme()->GetSystemColor(
|
||||
ui::NativeTheme::kColorId_ResultsTableNormalBackground));
|
||||
OnPaintBorder(draw_canvas);
|
||||
|
||||
for (int i = 0; i < popup_->GetLineCount(); ++i) {
|
||||
gfx::Rect line_rect = popup_->GetRowBounds(i);
|
||||
|
||||
DrawAutofillEntry(draw_canvas, i, line_rect);
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
if (view_proxy_.get()) {
|
||||
view_proxy_->SetBounds(popup_->popup_bounds_in_view());
|
||||
view_proxy_->SetBitmap(bitmap);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutofillPopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
|
||||
node_data->role = ax::mojom::Role::kMenu;
|
||||
node_data->SetName("Autofill Menu");
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnMouseCaptureLost() {
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
bool AutofillPopupView::OnMouseDragged(const ui::MouseEvent& event) {
|
||||
if (HitTestPoint(event.location())) {
|
||||
SetSelection(event.location());
|
||||
|
||||
// We must return true in order to get future OnMouseDragged and
|
||||
// OnMouseReleased events.
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we move off of the popup, we lose the selection.
|
||||
ClearSelection();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnMouseExited(const ui::MouseEvent& event) {
|
||||
// Pressing return causes the cursor to hide, which will generate an
|
||||
// OnMouseExited event. Pressing return should activate the current selection
|
||||
// via AcceleratorPressed, so we need to let that run first.
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&AutofillPopupView::ClearSelection,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnMouseMoved(const ui::MouseEvent& event) {
|
||||
// A synthesized mouse move will be sent when the popup is first shown.
|
||||
// Don't preview a suggestion if the mouse happens to be hovering there.
|
||||
#if defined(OS_WIN)
|
||||
if (base::Time::Now() - show_time_ <= base::TimeDelta::FromMilliseconds(50))
|
||||
return;
|
||||
#else
|
||||
if (event.flags() & ui::EF_IS_SYNTHESIZED)
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (HitTestPoint(event.location()))
|
||||
SetSelection(event.location());
|
||||
else
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
bool AutofillPopupView::OnMousePressed(const ui::MouseEvent& event) {
|
||||
return event.GetClickCount() == 1;
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnMouseReleased(const ui::MouseEvent& event) {
|
||||
// We only care about the left click.
|
||||
if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location()))
|
||||
AcceptSelection(event.location());
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnGestureEvent(ui::GestureEvent* event) {
|
||||
switch (event->type()) {
|
||||
case ui::ET_GESTURE_TAP_DOWN:
|
||||
case ui::ET_GESTURE_SCROLL_BEGIN:
|
||||
case ui::ET_GESTURE_SCROLL_UPDATE:
|
||||
if (HitTestPoint(event->location()))
|
||||
SetSelection(event->location());
|
||||
else
|
||||
ClearSelection();
|
||||
break;
|
||||
case ui::ET_GESTURE_TAP:
|
||||
case ui::ET_GESTURE_SCROLL_END:
|
||||
if (HitTestPoint(event->location()))
|
||||
AcceptSelection(event->location());
|
||||
else
|
||||
ClearSelection();
|
||||
break;
|
||||
case ui::ET_GESTURE_TAP_CANCEL:
|
||||
case ui::ET_SCROLL_FLING_START:
|
||||
ClearSelection();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
event->SetHandled();
|
||||
}
|
||||
|
||||
bool AutofillPopupView::AcceleratorPressed(const ui::Accelerator& accelerator) {
|
||||
if (accelerator.modifiers() != ui::EF_NONE)
|
||||
return false;
|
||||
|
||||
if (accelerator.key_code() == ui::VKEY_ESCAPE) {
|
||||
if (popup_)
|
||||
popup_->Hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (accelerator.key_code() == ui::VKEY_RETURN)
|
||||
return AcceptSelectedLine();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutofillPopupView::HandleKeyPressEvent(
|
||||
const content::NativeWebKeyboardEvent& event) {
|
||||
if (!popup_)
|
||||
return false;
|
||||
switch (event.windows_key_code) {
|
||||
case ui::VKEY_UP:
|
||||
SelectPreviousLine();
|
||||
return true;
|
||||
case ui::VKEY_DOWN:
|
||||
SelectNextLine();
|
||||
return true;
|
||||
case ui::VKEY_PRIOR: // Page up.
|
||||
SetSelectedLine(0);
|
||||
return true;
|
||||
case ui::VKEY_NEXT: // Page down.
|
||||
SetSelectedLine(popup_->GetLineCount() - 1);
|
||||
return true;
|
||||
case ui::VKEY_ESCAPE:
|
||||
popup_->Hide();
|
||||
return true;
|
||||
case ui::VKEY_TAB:
|
||||
// A tab press should cause the selected line to be accepted, but still
|
||||
// return false so the tab key press propagates and changes the cursor
|
||||
// location.
|
||||
AcceptSelectedLine();
|
||||
return false;
|
||||
case ui::VKEY_RETURN:
|
||||
return AcceptSelectedLine();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnNativeFocusChanged(gfx::NativeView focused_now) {
|
||||
if (GetWidget() && GetWidget()->GetNativeView() != focused_now && popup_)
|
||||
popup_->Hide();
|
||||
}
|
||||
|
||||
void AutofillPopupView::OnWidgetBoundsChanged(views::Widget* widget,
|
||||
const gfx::Rect& new_bounds) {
|
||||
if (widget != parent_widget_)
|
||||
return;
|
||||
if (popup_)
|
||||
popup_->Hide();
|
||||
}
|
||||
|
||||
void AutofillPopupView::AcceptSuggestion(int index) {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
popup_->AcceptSuggestion(index);
|
||||
popup_->Hide();
|
||||
}
|
||||
|
||||
bool AutofillPopupView::AcceptSelectedLine() {
|
||||
if (!selected_line_ || selected_line_.value() >= popup_->GetLineCount())
|
||||
return false;
|
||||
|
||||
AcceptSuggestion(selected_line_.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutofillPopupView::AcceptSelection(const gfx::Point& point) {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
SetSelectedLine(popup_->LineFromY(point.y()));
|
||||
AcceptSelectedLine();
|
||||
}
|
||||
|
||||
void AutofillPopupView::SetSelectedLine(base::Optional<int> selected_line) {
|
||||
if (!popup_)
|
||||
return;
|
||||
if (selected_line_ == selected_line)
|
||||
return;
|
||||
if (selected_line && selected_line.value() >= popup_->GetLineCount())
|
||||
return;
|
||||
|
||||
auto previous_selected_line(selected_line_);
|
||||
selected_line_ = selected_line;
|
||||
OnSelectedRowChanged(previous_selected_line, selected_line_);
|
||||
}
|
||||
|
||||
void AutofillPopupView::SetSelection(const gfx::Point& point) {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
SetSelectedLine(popup_->LineFromY(point.y()));
|
||||
}
|
||||
|
||||
void AutofillPopupView::SelectNextLine() {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
int new_selected_line = selected_line_ ? *selected_line_ + 1 : 0;
|
||||
if (new_selected_line >= popup_->GetLineCount())
|
||||
new_selected_line = 0;
|
||||
|
||||
SetSelectedLine(new_selected_line);
|
||||
}
|
||||
|
||||
void AutofillPopupView::SelectPreviousLine() {
|
||||
if (!popup_)
|
||||
return;
|
||||
|
||||
int new_selected_line = selected_line_.value_or(0) - 1;
|
||||
if (new_selected_line < 0)
|
||||
new_selected_line = popup_->GetLineCount() - 1;
|
||||
|
||||
SetSelectedLine(new_selected_line);
|
||||
}
|
||||
|
||||
void AutofillPopupView::ClearSelection() {
|
||||
SetSelectedLine(base::nullopt);
|
||||
}
|
||||
|
||||
void AutofillPopupView::RemoveObserver() {
|
||||
parent_widget_->RemoveObserver(this);
|
||||
views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this);
|
||||
}
|
||||
|
||||
} // namespace atom
|
155
shell/browser/ui/views/autofill_popup_view.h
Normal file
155
shell/browser/ui/views/autofill_popup_view.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) 2012 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.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "atom/browser/ui/autofill_popup.h"
|
||||
|
||||
#include "base/optional.h"
|
||||
#include "content/public/browser/native_web_keyboard_event.h"
|
||||
#include "content/public/browser/render_widget_host.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "ui/accessibility/ax_node_data.h"
|
||||
#include "ui/views/drag_controller.h"
|
||||
#include "ui/views/focus/widget_focus_manager.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/views/widget/widget_observer.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
#include "atom/browser/osr/osr_view_proxy.h"
|
||||
#endif
|
||||
|
||||
namespace atom {
|
||||
|
||||
const int kPopupBorderThickness = 1;
|
||||
const int kSmallerFontSizeDelta = -1;
|
||||
const int kEndPadding = 8;
|
||||
const int kNamePadding = 15;
|
||||
const int kRowHeight = 24;
|
||||
|
||||
class AutofillPopup;
|
||||
|
||||
// Child view only for triggering accessibility events. Rendering is handled
|
||||
// by |AutofillPopupViewViews|.
|
||||
class AutofillPopupChildView : public views::View {
|
||||
public:
|
||||
explicit AutofillPopupChildView(const base::string16& suggestion)
|
||||
: suggestion_(suggestion) {
|
||||
SetFocusBehavior(FocusBehavior::ALWAYS);
|
||||
}
|
||||
|
||||
private:
|
||||
~AutofillPopupChildView() override {}
|
||||
|
||||
// views::Views implementation
|
||||
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
|
||||
|
||||
base::string16 suggestion_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AutofillPopupChildView);
|
||||
};
|
||||
|
||||
class AutofillPopupView : public views::WidgetDelegateView,
|
||||
public views::WidgetFocusChangeListener,
|
||||
public views::WidgetObserver,
|
||||
public views::DragController {
|
||||
public:
|
||||
explicit AutofillPopupView(AutofillPopup* popup,
|
||||
views::Widget* parent_widget);
|
||||
~AutofillPopupView() override;
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
|
||||
void OnSuggestionsChanged();
|
||||
|
||||
int GetSelectedLine() { return selected_line_.value_or(-1); }
|
||||
|
||||
void WriteDragDataForView(views::View*,
|
||||
const gfx::Point&,
|
||||
ui::OSExchangeData*) override;
|
||||
int GetDragOperationsForView(views::View*, const gfx::Point&) override;
|
||||
bool CanStartDragForView(views::View*,
|
||||
const gfx::Point&,
|
||||
const gfx::Point&) override;
|
||||
|
||||
private:
|
||||
friend class AutofillPopup;
|
||||
|
||||
void OnSelectedRowChanged(base::Optional<int> previous_row_selection,
|
||||
base::Optional<int> current_row_selection);
|
||||
|
||||
// Draw the given autofill entry in |entry_rect|.
|
||||
void DrawAutofillEntry(gfx::Canvas* canvas,
|
||||
int index,
|
||||
const gfx::Rect& entry_rect);
|
||||
|
||||
// Creates child views based on the suggestions given by |controller_|. These
|
||||
// child views are used for accessibility events only. We need child views to
|
||||
// populate the correct |AXNodeData| when user selects a suggestion.
|
||||
void CreateChildViews();
|
||||
|
||||
void DoUpdateBoundsAndRedrawPopup();
|
||||
|
||||
// views::Views implementation.
|
||||
void OnPaint(gfx::Canvas* canvas) override;
|
||||
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
|
||||
void OnMouseCaptureLost() override;
|
||||
bool OnMouseDragged(const ui::MouseEvent& event) override;
|
||||
void OnMouseExited(const ui::MouseEvent& event) override;
|
||||
void OnMouseMoved(const ui::MouseEvent& event) override;
|
||||
bool OnMousePressed(const ui::MouseEvent& event) override;
|
||||
void OnMouseReleased(const ui::MouseEvent& event) override;
|
||||
void OnGestureEvent(ui::GestureEvent* event) override;
|
||||
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
|
||||
bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event);
|
||||
|
||||
// views::WidgetFocusChangeListener implementation.
|
||||
void OnNativeFocusChanged(gfx::NativeView focused_now) override;
|
||||
|
||||
// views::WidgetObserver implementation.
|
||||
void OnWidgetBoundsChanged(views::Widget* widget,
|
||||
const gfx::Rect& new_bounds) override;
|
||||
|
||||
void AcceptSuggestion(int index);
|
||||
bool AcceptSelectedLine();
|
||||
void AcceptSelection(const gfx::Point& point);
|
||||
void SetSelectedLine(base::Optional<int> selected_line);
|
||||
void SetSelection(const gfx::Point& point);
|
||||
void SelectNextLine();
|
||||
void SelectPreviousLine();
|
||||
void ClearSelection();
|
||||
|
||||
// Stop observing the widget.
|
||||
void RemoveObserver();
|
||||
|
||||
// Controller for this popup. Weak reference.
|
||||
AutofillPopup* popup_;
|
||||
|
||||
// The widget of the window that triggered this popup. Weak reference.
|
||||
views::Widget* parent_widget_;
|
||||
|
||||
// The time when the popup was shown.
|
||||
base::Time show_time_;
|
||||
|
||||
// The index of the currently selected line
|
||||
base::Optional<int> selected_line_;
|
||||
|
||||
#if BUILDFLAG(ENABLE_OSR)
|
||||
std::unique_ptr<OffscreenViewProxy> view_proxy_;
|
||||
#endif
|
||||
|
||||
// The registered keypress callback, responsible for switching lines on
|
||||
// key presses
|
||||
content::RenderWidgetHost::KeyPressEventCallback keypress_callback_;
|
||||
|
||||
base::WeakPtrFactory<AutofillPopupView> weak_ptr_factory_;
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_
|
114
shell/browser/ui/views/frameless_view.cc
Normal file
114
shell/browser/ui/views/frameless_view.cc
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/frameless_view.h"
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kResizeInsideBoundsSize = 5;
|
||||
const int kResizeAreaCornerSize = 16;
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
const char FramelessView::kViewClassName[] = "FramelessView";
|
||||
|
||||
FramelessView::FramelessView() {}
|
||||
|
||||
FramelessView::~FramelessView() {}
|
||||
|
||||
void FramelessView::Init(NativeWindowViews* window, views::Widget* frame) {
|
||||
window_ = window;
|
||||
frame_ = frame;
|
||||
}
|
||||
|
||||
int FramelessView::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
// Check the frame first, as we allow a small area overlapping the contents
|
||||
// to be used for resize handles.
|
||||
bool can_ever_resize = frame_->widget_delegate()
|
||||
? frame_->widget_delegate()->CanResize()
|
||||
: false;
|
||||
// Don't allow overlapping resize handles when the window is maximized or
|
||||
// fullscreen, as it can't be resized in those states.
|
||||
int resize_border = frame_->IsMaximized() || frame_->IsFullscreen()
|
||||
? 0
|
||||
: kResizeInsideBoundsSize;
|
||||
return GetHTComponentForFrame(point, resize_border, resize_border,
|
||||
kResizeAreaCornerSize, kResizeAreaCornerSize,
|
||||
can_ever_resize);
|
||||
}
|
||||
|
||||
gfx::Rect FramelessView::GetBoundsForClientView() const {
|
||||
return bounds();
|
||||
}
|
||||
|
||||
gfx::Rect FramelessView::GetWindowBoundsForClientBounds(
|
||||
const gfx::Rect& client_bounds) const {
|
||||
gfx::Rect window_bounds = client_bounds;
|
||||
// Enforce minimum size (1, 1) in case that client_bounds is passed with
|
||||
// empty size. This could occur when the frameless window is being
|
||||
// initialized.
|
||||
if (window_bounds.IsEmpty()) {
|
||||
window_bounds.set_width(1);
|
||||
window_bounds.set_height(1);
|
||||
}
|
||||
return window_bounds;
|
||||
}
|
||||
|
||||
int FramelessView::NonClientHitTest(const gfx::Point& cursor) {
|
||||
if (frame_->IsFullscreen())
|
||||
return HTCLIENT;
|
||||
|
||||
// Check for possible draggable region in the client area for the frameless
|
||||
// window.
|
||||
SkRegion* draggable_region = window_->draggable_region();
|
||||
if (draggable_region && draggable_region->contains(cursor.x(), cursor.y()))
|
||||
return HTCAPTION;
|
||||
|
||||
// Support resizing frameless window by dragging the border.
|
||||
int frame_component = ResizingBorderHitTest(cursor);
|
||||
if (frame_component != HTNOWHERE)
|
||||
return frame_component;
|
||||
|
||||
return HTCLIENT;
|
||||
}
|
||||
|
||||
void FramelessView::GetWindowMask(const gfx::Size& size, SkPath* window_mask) {}
|
||||
|
||||
void FramelessView::ResetWindowControls() {}
|
||||
|
||||
void FramelessView::UpdateWindowIcon() {}
|
||||
|
||||
void FramelessView::UpdateWindowTitle() {}
|
||||
|
||||
void FramelessView::SizeConstraintsChanged() {}
|
||||
|
||||
gfx::Size FramelessView::CalculatePreferredSize() const {
|
||||
return frame_->non_client_view()
|
||||
->GetWindowBoundsForClientBounds(
|
||||
gfx::Rect(frame_->client_view()->GetPreferredSize()))
|
||||
.size();
|
||||
}
|
||||
|
||||
gfx::Size FramelessView::GetMinimumSize() const {
|
||||
return window_->GetContentMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size FramelessView::GetMaximumSize() const {
|
||||
return window_->GetContentMaximumSize();
|
||||
}
|
||||
|
||||
const char* FramelessView::GetClassName() const {
|
||||
return kViewClassName;
|
||||
}
|
||||
|
||||
} // namespace atom
|
57
shell/browser/ui/views/frameless_view.h
Normal file
57
shell/browser/ui/views/frameless_view.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_FRAMELESS_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_FRAMELESS_VIEW_H_
|
||||
|
||||
#include "ui/views/window/non_client_view.h"
|
||||
|
||||
namespace views {
|
||||
class Widget;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowViews;
|
||||
|
||||
class FramelessView : public views::NonClientFrameView {
|
||||
public:
|
||||
static const char kViewClassName[];
|
||||
FramelessView();
|
||||
~FramelessView() override;
|
||||
|
||||
virtual void Init(NativeWindowViews* window, views::Widget* frame);
|
||||
|
||||
// Returns whether the |point| is on frameless window's resizing border.
|
||||
int ResizingBorderHitTest(const gfx::Point& point);
|
||||
|
||||
protected:
|
||||
// views::NonClientFrameView:
|
||||
gfx::Rect GetBoundsForClientView() const override;
|
||||
gfx::Rect GetWindowBoundsForClientBounds(
|
||||
const gfx::Rect& client_bounds) const override;
|
||||
int NonClientHitTest(const gfx::Point& point) override;
|
||||
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override;
|
||||
void ResetWindowControls() override;
|
||||
void UpdateWindowIcon() override;
|
||||
void UpdateWindowTitle() override;
|
||||
void SizeConstraintsChanged() override;
|
||||
|
||||
// Overridden from View:
|
||||
gfx::Size CalculatePreferredSize() const override;
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
const char* GetClassName() const override;
|
||||
|
||||
// Not owned.
|
||||
NativeWindowViews* window_ = nullptr;
|
||||
views::Widget* frame_ = nullptr;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FramelessView);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_FRAMELESS_VIEW_H_
|
336
shell/browser/ui/views/global_menu_bar_x11.cc
Normal file
336
shell/browser/ui/views/global_menu_bar_x11.cc
Normal file
|
@ -0,0 +1,336 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/global_menu_bar_x11.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/aura/window_tree_host.h"
|
||||
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
|
||||
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
|
||||
#include "ui/gfx/x/x11.h"
|
||||
|
||||
// libdbusmenu-glib types
|
||||
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)();
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_with_id_func)(int id);
|
||||
|
||||
typedef int (*dbusmenu_menuitem_get_id_func)(DbusmenuMenuitem* item);
|
||||
typedef GList* (*dbusmenu_menuitem_get_children_func)(DbusmenuMenuitem* item);
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)(
|
||||
DbusmenuMenuitem* parent,
|
||||
DbusmenuMenuitem* child);
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)(
|
||||
DbusmenuMenuitem* item,
|
||||
const char* property,
|
||||
const char* value);
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)(
|
||||
DbusmenuMenuitem* item,
|
||||
const char* property,
|
||||
GVariant* value);
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)(
|
||||
DbusmenuMenuitem* item,
|
||||
const char* property,
|
||||
bool value);
|
||||
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)(
|
||||
DbusmenuMenuitem* item,
|
||||
const char* property,
|
||||
int value);
|
||||
|
||||
typedef struct _DbusmenuServer DbusmenuServer;
|
||||
typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object);
|
||||
typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self,
|
||||
DbusmenuMenuitem* root);
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// Retrieved functions from libdbusmenu-glib.
|
||||
|
||||
// DbusmenuMenuItem methods:
|
||||
dbusmenu_menuitem_new_func menuitem_new = NULL;
|
||||
dbusmenu_menuitem_new_with_id_func menuitem_new_with_id = NULL;
|
||||
dbusmenu_menuitem_get_id_func menuitem_get_id = NULL;
|
||||
dbusmenu_menuitem_get_children_func menuitem_get_children = NULL;
|
||||
dbusmenu_menuitem_get_children_func menuitem_take_children = NULL;
|
||||
dbusmenu_menuitem_child_append_func menuitem_child_append = NULL;
|
||||
dbusmenu_menuitem_property_set_func menuitem_property_set = NULL;
|
||||
dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant =
|
||||
NULL;
|
||||
dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL;
|
||||
dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL;
|
||||
|
||||
// DbusmenuServer methods:
|
||||
dbusmenu_server_new_func server_new = NULL;
|
||||
dbusmenu_server_set_root_func server_set_root = NULL;
|
||||
|
||||
// Properties that we set on menu items:
|
||||
const char kPropertyEnabled[] = "enabled";
|
||||
const char kPropertyLabel[] = "label";
|
||||
const char kPropertyShortcut[] = "shortcut";
|
||||
const char kPropertyType[] = "type";
|
||||
const char kPropertyToggleType[] = "toggle-type";
|
||||
const char kPropertyToggleState[] = "toggle-state";
|
||||
const char kPropertyVisible[] = "visible";
|
||||
const char kPropertyChildrenDisplay[] = "children-display";
|
||||
|
||||
const char kToggleCheck[] = "checkmark";
|
||||
const char kToggleRadio[] = "radio";
|
||||
const char kTypeSeparator[] = "separator";
|
||||
const char kDisplaySubmenu[] = "submenu";
|
||||
|
||||
void EnsureMethodsLoaded() {
|
||||
static bool attempted_load = false;
|
||||
if (attempted_load)
|
||||
return;
|
||||
attempted_load = true;
|
||||
|
||||
void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY);
|
||||
if (!dbusmenu_lib)
|
||||
dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY);
|
||||
if (!dbusmenu_lib)
|
||||
return;
|
||||
|
||||
// DbusmenuMenuItem methods.
|
||||
menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_new"));
|
||||
menuitem_new_with_id = reinterpret_cast<dbusmenu_menuitem_new_with_id_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_new_with_id"));
|
||||
menuitem_get_id = reinterpret_cast<dbusmenu_menuitem_get_id_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_id"));
|
||||
menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children"));
|
||||
menuitem_take_children =
|
||||
reinterpret_cast<dbusmenu_menuitem_get_children_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_take_children"));
|
||||
menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append"));
|
||||
menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set"));
|
||||
menuitem_property_set_variant =
|
||||
reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant"));
|
||||
menuitem_property_set_bool =
|
||||
reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool"));
|
||||
menuitem_property_set_int =
|
||||
reinterpret_cast<dbusmenu_menuitem_property_set_int_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int"));
|
||||
|
||||
// DbusmenuServer methods.
|
||||
server_new = reinterpret_cast<dbusmenu_server_new_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_server_new"));
|
||||
server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>(
|
||||
dlsym(dbusmenu_lib, "dbusmenu_server_set_root"));
|
||||
}
|
||||
|
||||
AtomMenuModel* ModelForMenuItem(DbusmenuMenuitem* item) {
|
||||
return reinterpret_cast<AtomMenuModel*>(
|
||||
g_object_get_data(G_OBJECT(item), "model"));
|
||||
}
|
||||
|
||||
bool GetMenuItemID(DbusmenuMenuitem* item, int* id) {
|
||||
gpointer id_ptr = g_object_get_data(G_OBJECT(item), "menu-id");
|
||||
if (id_ptr != NULL) {
|
||||
*id = GPOINTER_TO_INT(id_ptr) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetMenuItemID(DbusmenuMenuitem* item, int id) {
|
||||
DCHECK_GE(id, 0);
|
||||
|
||||
// Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
|
||||
g_object_set_data(G_OBJECT(item), "menu-id", GINT_TO_POINTER(id + 1));
|
||||
}
|
||||
|
||||
std::string GetMenuModelStatus(AtomMenuModel* model) {
|
||||
std::string ret;
|
||||
for (int i = 0; i < model->GetItemCount(); ++i) {
|
||||
int status = model->GetTypeAt(i) | (model->IsVisibleAt(i) << 3) |
|
||||
(model->IsEnabledAt(i) << 4) |
|
||||
(model->IsItemCheckedAt(i) << 5);
|
||||
ret += base::StringPrintf(
|
||||
"%s-%X\n", base::UTF16ToUTF8(model->GetLabelAt(i)).c_str(), status);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GlobalMenuBarX11::GlobalMenuBarX11(NativeWindowViews* window)
|
||||
: window_(window),
|
||||
xid_(window_->GetNativeWindow()->GetHost()->GetAcceleratedWidget()) {
|
||||
EnsureMethodsLoaded();
|
||||
if (server_new)
|
||||
InitServer(xid_);
|
||||
|
||||
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid_);
|
||||
}
|
||||
|
||||
GlobalMenuBarX11::~GlobalMenuBarX11() {
|
||||
if (IsServerStarted())
|
||||
g_object_unref(server_);
|
||||
|
||||
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid_);
|
||||
}
|
||||
|
||||
// static
|
||||
std::string GlobalMenuBarX11::GetPathForWindow(gfx::AcceleratedWidget xid) {
|
||||
return base::StringPrintf("/com/canonical/menu/%lX", xid);
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::SetMenu(AtomMenuModel* menu_model) {
|
||||
if (!IsServerStarted())
|
||||
return;
|
||||
|
||||
DbusmenuMenuitem* root_item = menuitem_new();
|
||||
menuitem_property_set(root_item, kPropertyLabel, "Root");
|
||||
menuitem_property_set_bool(root_item, kPropertyVisible, true);
|
||||
if (menu_model != nullptr) {
|
||||
BuildMenuFromModel(menu_model, root_item);
|
||||
}
|
||||
|
||||
server_set_root(server_, root_item);
|
||||
g_object_unref(root_item);
|
||||
}
|
||||
|
||||
bool GlobalMenuBarX11::IsServerStarted() const {
|
||||
return server_;
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::InitServer(gfx::AcceleratedWidget xid) {
|
||||
std::string path = GetPathForWindow(xid);
|
||||
server_ = server_new(path.c_str());
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::OnWindowMapped() {
|
||||
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid_);
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::OnWindowUnmapped() {
|
||||
GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid_);
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::BuildMenuFromModel(AtomMenuModel* model,
|
||||
DbusmenuMenuitem* parent) {
|
||||
for (int i = 0; i < model->GetItemCount(); ++i) {
|
||||
DbusmenuMenuitem* item = menuitem_new();
|
||||
menuitem_property_set_bool(item, kPropertyVisible, model->IsVisibleAt(i));
|
||||
|
||||
AtomMenuModel::ItemType type = model->GetTypeAt(i);
|
||||
if (type == AtomMenuModel::TYPE_SEPARATOR) {
|
||||
menuitem_property_set(item, kPropertyType, kTypeSeparator);
|
||||
} else {
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(i)));
|
||||
menuitem_property_set(item, kPropertyLabel, label.c_str());
|
||||
menuitem_property_set_bool(item, kPropertyEnabled, model->IsEnabledAt(i));
|
||||
|
||||
g_object_set_data(G_OBJECT(item), "model", model);
|
||||
SetMenuItemID(item, i);
|
||||
|
||||
if (type == AtomMenuModel::TYPE_SUBMENU) {
|
||||
menuitem_property_set(item, kPropertyChildrenDisplay, kDisplaySubmenu);
|
||||
g_signal_connect(item, "about-to-show", G_CALLBACK(OnSubMenuShowThunk),
|
||||
this);
|
||||
} else {
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAtWithParams(i, true, &accelerator))
|
||||
RegisterAccelerator(item, accelerator);
|
||||
|
||||
g_signal_connect(item, "item-activated",
|
||||
G_CALLBACK(OnItemActivatedThunk), this);
|
||||
|
||||
if (type == AtomMenuModel::TYPE_CHECK ||
|
||||
type == AtomMenuModel::TYPE_RADIO) {
|
||||
menuitem_property_set(
|
||||
item, kPropertyToggleType,
|
||||
type == AtomMenuModel::TYPE_CHECK ? kToggleCheck : kToggleRadio);
|
||||
menuitem_property_set_int(item, kPropertyToggleState,
|
||||
model->IsItemCheckedAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuitem_child_append(parent, item);
|
||||
g_object_unref(item);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item,
|
||||
const ui::Accelerator& accelerator) {
|
||||
// A translation of libdbusmenu-gtk's menuitem_property_set_shortcut()
|
||||
// translated from GDK types to ui::Accelerator types.
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
|
||||
|
||||
if (accelerator.IsCtrlDown())
|
||||
g_variant_builder_add(&builder, "s", "Control");
|
||||
if (accelerator.IsAltDown())
|
||||
g_variant_builder_add(&builder, "s", "Alt");
|
||||
if (accelerator.IsShiftDown())
|
||||
g_variant_builder_add(&builder, "s", "Shift");
|
||||
|
||||
char* name =
|
||||
XKeysymToString(XKeysymForWindowsKeyCode(accelerator.key_code(), false));
|
||||
if (!name) {
|
||||
NOTIMPLEMENTED();
|
||||
return;
|
||||
}
|
||||
g_variant_builder_add(&builder, "s", name);
|
||||
|
||||
GVariant* inside_array = g_variant_builder_end(&builder);
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
|
||||
g_variant_builder_add_value(&builder, inside_array);
|
||||
GVariant* outside_array = g_variant_builder_end(&builder);
|
||||
|
||||
menuitem_property_set_variant(item, kPropertyShortcut, outside_array);
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item,
|
||||
unsigned int timestamp) {
|
||||
int id;
|
||||
AtomMenuModel* model = ModelForMenuItem(item);
|
||||
if (model && GetMenuItemID(item, &id))
|
||||
model->ActivatedAt(id, 0);
|
||||
}
|
||||
|
||||
void GlobalMenuBarX11::OnSubMenuShow(DbusmenuMenuitem* item) {
|
||||
int id;
|
||||
AtomMenuModel* model = ModelForMenuItem(item);
|
||||
if (!model || !GetMenuItemID(item, &id))
|
||||
return;
|
||||
|
||||
// Do not update menu if the submenu has not been changed.
|
||||
std::string status = GetMenuModelStatus(model);
|
||||
char* old = static_cast<char*>(g_object_get_data(G_OBJECT(item), "status"));
|
||||
if (old && status == old)
|
||||
return;
|
||||
|
||||
// Save the new status.
|
||||
g_object_set_data_full(G_OBJECT(item), "status", g_strdup(status.c_str()),
|
||||
g_free);
|
||||
|
||||
// Clear children.
|
||||
GList* children = menuitem_take_children(item);
|
||||
g_list_foreach(children, reinterpret_cast<GFunc>(g_object_unref), NULL);
|
||||
g_list_free(children);
|
||||
|
||||
// Build children.
|
||||
BuildMenuFromModel(model->GetSubmenuModelAt(id), item);
|
||||
}
|
||||
|
||||
} // namespace atom
|
81
shell/browser/ui/views/global_menu_bar_x11.h
Normal file
81
shell/browser/ui/views/global_menu_bar_x11.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_X11_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_X11_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/macros.h"
|
||||
#include "ui/base/glib/glib_signal.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
|
||||
typedef struct _DbusmenuServer DbusmenuServer;
|
||||
|
||||
namespace ui {
|
||||
class Accelerator;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindowViews;
|
||||
|
||||
// Controls the Mac style menu bar on Unity.
|
||||
//
|
||||
// Unity has an Apple-like menu bar at the top of the screen that changes
|
||||
// depending on the active window. In the GTK port, we had a hidden GtkMenuBar
|
||||
// object in each GtkWindow which existed only to be scrapped by the
|
||||
// libdbusmenu-gtk code. Since we don't have GtkWindows anymore, we need to
|
||||
// interface directly with the lower level libdbusmenu-glib, which we
|
||||
// opportunistically dlopen() since not everyone is running Ubuntu.
|
||||
//
|
||||
// This class is like the chrome's corresponding one, but it generates the menu
|
||||
// from menu models instead, and it is also per-window specific.
|
||||
class GlobalMenuBarX11 {
|
||||
public:
|
||||
explicit GlobalMenuBarX11(NativeWindowViews* window);
|
||||
virtual ~GlobalMenuBarX11();
|
||||
|
||||
// Creates the object path for DbusmenuServer which is attached to |xid|.
|
||||
static std::string GetPathForWindow(gfx::AcceleratedWidget xid);
|
||||
|
||||
void SetMenu(AtomMenuModel* menu_model);
|
||||
bool IsServerStarted() const;
|
||||
|
||||
// Called by NativeWindow when it show/hides.
|
||||
void OnWindowMapped();
|
||||
void OnWindowUnmapped();
|
||||
|
||||
private:
|
||||
// Creates a DbusmenuServer.
|
||||
void InitServer(gfx::AcceleratedWidget xid);
|
||||
|
||||
// Create a menu from menu model.
|
||||
void BuildMenuFromModel(AtomMenuModel* model, DbusmenuMenuitem* parent);
|
||||
|
||||
// Sets the accelerator for |item|.
|
||||
void RegisterAccelerator(DbusmenuMenuitem* item,
|
||||
const ui::Accelerator& accelerator);
|
||||
|
||||
CHROMEG_CALLBACK_1(GlobalMenuBarX11,
|
||||
void,
|
||||
OnItemActivated,
|
||||
DbusmenuMenuitem*,
|
||||
unsigned int);
|
||||
CHROMEG_CALLBACK_0(GlobalMenuBarX11, void, OnSubMenuShow, DbusmenuMenuitem*);
|
||||
|
||||
NativeWindowViews* window_;
|
||||
gfx::AcceleratedWidget xid_;
|
||||
|
||||
DbusmenuServer* server_ = nullptr;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(GlobalMenuBarX11);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_X11_H_
|
235
shell/browser/ui/views/inspectable_web_contents_view_views.cc
Normal file
235
shell/browser/ui/views/inspectable_web_contents_view_views.cc
Normal file
|
@ -0,0 +1,235 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/views/inspectable_web_contents_view_views.h"
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_delegate.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_impl.h"
|
||||
#include "atom/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "ui/views/controls/label.h"
|
||||
#include "ui/views/controls/webview/webview.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/views/window/client_view.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
class DevToolsWindowDelegate : public views::ClientView,
|
||||
public views::WidgetDelegate {
|
||||
public:
|
||||
DevToolsWindowDelegate(InspectableWebContentsViewViews* shell,
|
||||
views::View* view,
|
||||
views::Widget* widget)
|
||||
: views::ClientView(widget, view),
|
||||
shell_(shell),
|
||||
view_(view),
|
||||
widget_(widget) {
|
||||
// A WidgetDelegate should be deleted on DeleteDelegate.
|
||||
set_owned_by_client();
|
||||
|
||||
if (shell->GetDelegate())
|
||||
icon_ = shell->GetDelegate()->GetDevToolsWindowIcon();
|
||||
}
|
||||
~DevToolsWindowDelegate() override {}
|
||||
|
||||
// views::WidgetDelegate:
|
||||
void DeleteDelegate() override { delete this; }
|
||||
views::View* GetInitiallyFocusedView() override { return view_; }
|
||||
bool CanResize() const override { return true; }
|
||||
bool CanMaximize() const override { return true; }
|
||||
bool CanMinimize() const override { return true; }
|
||||
base::string16 GetWindowTitle() const override { return shell_->GetTitle(); }
|
||||
gfx::ImageSkia GetWindowAppIcon() override { return GetWindowIcon(); }
|
||||
gfx::ImageSkia GetWindowIcon() override { return icon_; }
|
||||
views::Widget* GetWidget() override { return widget_; }
|
||||
const views::Widget* GetWidget() const override { return widget_; }
|
||||
views::View* GetContentsView() override { return view_; }
|
||||
views::ClientView* CreateClientView(views::Widget* widget) override {
|
||||
return this;
|
||||
}
|
||||
|
||||
// views::ClientView:
|
||||
bool CanClose() override {
|
||||
shell_->inspectable_web_contents()->CloseDevTools();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
InspectableWebContentsViewViews* shell_;
|
||||
views::View* view_;
|
||||
views::Widget* widget_;
|
||||
gfx::ImageSkia icon_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DevToolsWindowDelegate);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
InspectableWebContentsView* CreateInspectableContentsView(
|
||||
InspectableWebContentsImpl* inspectable_web_contents) {
|
||||
return new InspectableWebContentsViewViews(inspectable_web_contents);
|
||||
}
|
||||
|
||||
InspectableWebContentsViewViews::InspectableWebContentsViewViews(
|
||||
InspectableWebContentsImpl* inspectable_web_contents)
|
||||
: inspectable_web_contents_(inspectable_web_contents),
|
||||
devtools_window_web_view_(nullptr),
|
||||
contents_web_view_(nullptr),
|
||||
devtools_web_view_(new views::WebView(nullptr)),
|
||||
devtools_visible_(false),
|
||||
devtools_window_delegate_(nullptr),
|
||||
title_(base::ASCIIToUTF16("Developer Tools")) {
|
||||
set_owned_by_client();
|
||||
|
||||
if (!inspectable_web_contents_->IsGuest() &&
|
||||
inspectable_web_contents_->GetWebContents()->GetNativeView()) {
|
||||
views::WebView* contents_web_view = new views::WebView(nullptr);
|
||||
contents_web_view->SetWebContents(
|
||||
inspectable_web_contents_->GetWebContents());
|
||||
contents_web_view_ = contents_web_view;
|
||||
} else {
|
||||
contents_web_view_ =
|
||||
new views::Label(base::ASCIIToUTF16("No content under offscreen mode"));
|
||||
}
|
||||
|
||||
devtools_web_view_->SetVisible(false);
|
||||
AddChildView(devtools_web_view_);
|
||||
AddChildView(contents_web_view_);
|
||||
}
|
||||
|
||||
InspectableWebContentsViewViews::~InspectableWebContentsViewViews() {
|
||||
if (devtools_window_)
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
}
|
||||
|
||||
views::View* InspectableWebContentsViewViews::GetView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
views::View* InspectableWebContentsViewViews::GetWebView() {
|
||||
return contents_web_view_;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::ShowDevTools(bool activate) {
|
||||
if (devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = true;
|
||||
if (devtools_window_) {
|
||||
devtools_window_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_window_->SetBounds(
|
||||
inspectable_web_contents()->GetDevToolsBounds());
|
||||
if (activate) {
|
||||
devtools_window_->Show();
|
||||
} else {
|
||||
devtools_window_->ShowInactive();
|
||||
}
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(true);
|
||||
devtools_web_view_->SetWebContents(
|
||||
inspectable_web_contents_->GetDevToolsWebContents());
|
||||
devtools_web_view_->RequestFocus();
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::CloseDevTools() {
|
||||
if (!devtools_visible_)
|
||||
return;
|
||||
|
||||
devtools_visible_ = false;
|
||||
if (devtools_window_) {
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
devtools_window_.reset();
|
||||
devtools_window_web_view_ = nullptr;
|
||||
devtools_window_delegate_ = nullptr;
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(false);
|
||||
devtools_web_view_->SetWebContents(NULL);
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewViews::IsDevToolsViewShowing() {
|
||||
return devtools_visible_;
|
||||
}
|
||||
|
||||
bool InspectableWebContentsViewViews::IsDevToolsViewFocused() {
|
||||
if (devtools_window_web_view_)
|
||||
return devtools_window_web_view_->HasFocus();
|
||||
else if (devtools_web_view_)
|
||||
return devtools_web_view_->HasFocus();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetIsDocked(bool docked, bool activate) {
|
||||
CloseDevTools();
|
||||
|
||||
if (!docked) {
|
||||
devtools_window_.reset(new views::Widget);
|
||||
devtools_window_web_view_ = new views::WebView(NULL);
|
||||
devtools_window_delegate_ = new DevToolsWindowDelegate(
|
||||
this, devtools_window_web_view_, devtools_window_.get());
|
||||
|
||||
views::Widget::InitParams params;
|
||||
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
|
||||
params.delegate = devtools_window_delegate_;
|
||||
params.bounds = inspectable_web_contents()->GetDevToolsBounds();
|
||||
|
||||
#if defined(USE_X11)
|
||||
params.wm_role_name = "devtools";
|
||||
if (GetDelegate())
|
||||
GetDelegate()->GetDevToolsWindowWMClass(¶ms.wm_class_name,
|
||||
¶ms.wm_class_class);
|
||||
#endif
|
||||
|
||||
devtools_window_->Init(params);
|
||||
devtools_window_->UpdateWindowIcon();
|
||||
}
|
||||
|
||||
ShowDevTools(activate);
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) {
|
||||
strategy_.CopyFrom(strategy);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::SetTitle(const base::string16& title) {
|
||||
if (devtools_window_) {
|
||||
title_ = title;
|
||||
devtools_window_->UpdateWindowTitle();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectableWebContentsViewViews::Layout() {
|
||||
if (!devtools_web_view_->GetVisible()) {
|
||||
contents_web_view_->SetBoundsRect(GetContentsBounds());
|
||||
return;
|
||||
}
|
||||
|
||||
gfx::Size container_size(width(), height());
|
||||
gfx::Rect new_devtools_bounds;
|
||||
gfx::Rect new_contents_bounds;
|
||||
ApplyDevToolsContentsResizingStrategy(
|
||||
strategy_, container_size, &new_devtools_bounds, &new_contents_bounds);
|
||||
|
||||
// DevTools cares about the specific position, so we have to compensate RTL
|
||||
// layout here.
|
||||
new_devtools_bounds.set_x(GetMirroredXForRect(new_devtools_bounds));
|
||||
new_contents_bounds.set_x(GetMirroredXForRect(new_contents_bounds));
|
||||
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
contents_web_view_->SetBoundsRect(new_contents_bounds);
|
||||
}
|
||||
|
||||
} // namespace atom
|
72
shell/browser/ui/views/inspectable_web_contents_view_views.h
Normal file
72
shell/browser/ui/views/inspectable_web_contents_view_views.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 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-CHROMIUM file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "atom/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "chrome/browser/devtools/devtools_contents_resizing_strategy.h"
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace views {
|
||||
class WebView;
|
||||
class Widget;
|
||||
class WidgetDelegate;
|
||||
} // namespace views
|
||||
|
||||
namespace atom {
|
||||
|
||||
class InspectableWebContentsImpl;
|
||||
|
||||
class InspectableWebContentsViewViews : public InspectableWebContentsView,
|
||||
public views::View {
|
||||
public:
|
||||
explicit InspectableWebContentsViewViews(
|
||||
InspectableWebContentsImpl* inspectable_web_contents_impl);
|
||||
~InspectableWebContentsViewViews() override;
|
||||
|
||||
// InspectableWebContentsView:
|
||||
views::View* GetView() override;
|
||||
views::View* GetWebView() override;
|
||||
void ShowDevTools(bool activate) override;
|
||||
void CloseDevTools() override;
|
||||
bool IsDevToolsViewShowing() override;
|
||||
bool IsDevToolsViewFocused() override;
|
||||
void SetIsDocked(bool docked, bool activate) override;
|
||||
void SetContentsResizingStrategy(
|
||||
const DevToolsContentsResizingStrategy& strategy) override;
|
||||
void SetTitle(const base::string16& title) override;
|
||||
|
||||
InspectableWebContentsImpl* inspectable_web_contents() {
|
||||
return inspectable_web_contents_;
|
||||
}
|
||||
|
||||
const base::string16& GetTitle() const { return title_; }
|
||||
|
||||
private:
|
||||
// views::View:
|
||||
void Layout() override;
|
||||
|
||||
// Owns us.
|
||||
InspectableWebContentsImpl* inspectable_web_contents_;
|
||||
|
||||
std::unique_ptr<views::Widget> devtools_window_;
|
||||
views::WebView* devtools_window_web_view_;
|
||||
views::View* contents_web_view_;
|
||||
views::WebView* devtools_web_view_;
|
||||
|
||||
DevToolsContentsResizingStrategy strategy_;
|
||||
bool devtools_visible_;
|
||||
views::WidgetDelegate* devtools_window_delegate_;
|
||||
base::string16 title_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InspectableWebContentsViewViews);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_INSPECTABLE_WEB_CONTENTS_VIEW_VIEWS_H_
|
349
shell/browser/ui/views/menu_bar.cc
Normal file
349
shell/browser/ui/views/menu_bar.cc
Normal file
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/menu_bar.h"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "atom/browser/ui/views/submenu_button.h"
|
||||
#include "atom/common/keyboard_util.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/layout/box_layout.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
#if defined(USE_X11)
|
||||
#include "chrome/browser/ui/libgtkui/gtk_util.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "ui/gfx/color_utils.h"
|
||||
#endif
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// Default color of the menu bar.
|
||||
const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233);
|
||||
|
||||
} // namespace
|
||||
|
||||
const char MenuBar::kViewClassName[] = "ElectronMenuBar";
|
||||
|
||||
MenuBarColorUpdater::MenuBarColorUpdater(MenuBar* menu_bar)
|
||||
: menu_bar_(menu_bar) {}
|
||||
|
||||
MenuBarColorUpdater::~MenuBarColorUpdater() {}
|
||||
|
||||
void MenuBarColorUpdater::OnDidChangeFocus(views::View* focused_before,
|
||||
views::View* focused_now) {
|
||||
if (menu_bar_) {
|
||||
// if we've changed window focus, update menu bar colors
|
||||
const auto had_focus = menu_bar_->has_focus_;
|
||||
menu_bar_->has_focus_ = focused_now != nullptr;
|
||||
if (menu_bar_->has_focus_ != had_focus)
|
||||
menu_bar_->UpdateViewColors();
|
||||
}
|
||||
}
|
||||
|
||||
MenuBar::MenuBar(RootView* window)
|
||||
: background_color_(kDefaultColor),
|
||||
window_(window),
|
||||
color_updater_(new MenuBarColorUpdater(this)) {
|
||||
RefreshColorCache();
|
||||
UpdateViewColors();
|
||||
SetFocusBehavior(FocusBehavior::ALWAYS);
|
||||
SetLayoutManager(
|
||||
std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
|
||||
window_->GetFocusManager()->AddFocusChangeListener(color_updater_.get());
|
||||
}
|
||||
|
||||
MenuBar::~MenuBar() {
|
||||
window_->GetFocusManager()->RemoveFocusChangeListener(color_updater_.get());
|
||||
}
|
||||
|
||||
void MenuBar::SetMenu(AtomMenuModel* model) {
|
||||
menu_model_ = model;
|
||||
RebuildChildren();
|
||||
}
|
||||
|
||||
void MenuBar::SetAcceleratorVisibility(bool visible) {
|
||||
for (auto* child : GetChildrenInZOrder())
|
||||
static_cast<SubmenuButton*>(child)->SetAcceleratorVisibility(visible);
|
||||
}
|
||||
|
||||
MenuBar::View* MenuBar::FindAccelChild(base::char16 key) {
|
||||
for (auto* child : GetChildrenInZOrder()) {
|
||||
if (static_cast<SubmenuButton*>(child)->accelerator() == key)
|
||||
return child;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool MenuBar::HasAccelerator(base::char16 key) {
|
||||
return FindAccelChild(key) != nullptr;
|
||||
}
|
||||
|
||||
void MenuBar::ActivateAccelerator(base::char16 key) {
|
||||
auto* child = FindAccelChild(key);
|
||||
if (child)
|
||||
static_cast<SubmenuButton*>(child)->Activate(nullptr);
|
||||
}
|
||||
|
||||
int MenuBar::GetItemCount() const {
|
||||
return menu_model_ ? menu_model_->GetItemCount() : 0;
|
||||
}
|
||||
|
||||
bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& screenPoint,
|
||||
AtomMenuModel** menu_model,
|
||||
views::MenuButton** button) {
|
||||
if (!GetBoundsInScreen().Contains(screenPoint))
|
||||
return false;
|
||||
|
||||
auto children = GetChildrenInZOrder();
|
||||
for (int i = 0, n = children.size(); i < n; ++i) {
|
||||
if (children[i]->GetBoundsInScreen().Contains(screenPoint) &&
|
||||
(menu_model_->GetTypeAt(i) == AtomMenuModel::TYPE_SUBMENU)) {
|
||||
*menu_model = menu_model_->GetSubmenuModelAt(i);
|
||||
*button = static_cast<views::MenuButton*>(children[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MenuBar::OnBeforeExecuteCommand() {
|
||||
if (GetPaneFocusTraversable() != nullptr) {
|
||||
RemovePaneFocus();
|
||||
}
|
||||
window_->RestoreFocus();
|
||||
}
|
||||
|
||||
void MenuBar::OnMenuClosed() {
|
||||
SetAcceleratorVisibility(true);
|
||||
}
|
||||
|
||||
bool MenuBar::AcceleratorPressed(const ui::Accelerator& accelerator) {
|
||||
views::View* focused_view = GetFocusManager()->GetFocusedView();
|
||||
if (!ContainsForFocusSearch(this, focused_view))
|
||||
return false;
|
||||
|
||||
switch (accelerator.key_code()) {
|
||||
case ui::VKEY_MENU:
|
||||
case ui::VKEY_ESCAPE: {
|
||||
RemovePaneFocus();
|
||||
window_->RestoreFocus();
|
||||
return true;
|
||||
}
|
||||
case ui::VKEY_LEFT:
|
||||
GetFocusManager()->AdvanceFocus(true);
|
||||
return true;
|
||||
case ui::VKEY_RIGHT:
|
||||
GetFocusManager()->AdvanceFocus(false);
|
||||
return true;
|
||||
case ui::VKEY_HOME:
|
||||
GetFocusManager()->SetFocusedViewWithReason(
|
||||
GetFirstFocusableChild(), views::FocusManager::kReasonFocusTraversal);
|
||||
return true;
|
||||
case ui::VKEY_END:
|
||||
GetFocusManager()->SetFocusedViewWithReason(
|
||||
GetLastFocusableChild(), views::FocusManager::kReasonFocusTraversal);
|
||||
return true;
|
||||
default: {
|
||||
auto children = GetChildrenInZOrder();
|
||||
for (int i = 0, n = children.size(); i < n; ++i) {
|
||||
auto* button = static_cast<SubmenuButton*>(children[i]);
|
||||
bool shifted = false;
|
||||
auto keycode =
|
||||
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
|
||||
|
||||
if (keycode == accelerator.key_code()) {
|
||||
const gfx::Point p(0, 0);
|
||||
auto event = accelerator.ToKeyEvent();
|
||||
OnMenuButtonClicked(button, p, &event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MenuBar::SetPaneFocus(views::View* initial_focus) {
|
||||
// TODO(zcbenz): Submit patch to upstream Chromium to fix the crash.
|
||||
//
|
||||
// Without this check, Electron would crash when running tests.
|
||||
//
|
||||
// Check failed: rules_->CanFocusWindow(window, nullptr).
|
||||
// logging::LogMessage::~LogMessage
|
||||
// wm::FocusController::SetFocusedWindow
|
||||
// wm::FocusController::ResetFocusWithinActiveWindow
|
||||
// views::View::OnFocus
|
||||
// views::Button::OnFocus
|
||||
// views::LabelButton::OnFocus
|
||||
// views::View::Focus
|
||||
// views::FocusManager::SetFocusedViewWithReason
|
||||
// views::AccessiblePaneView::SetPaneFocus
|
||||
// atom::MenuBar::SetPaneFocus
|
||||
if (initial_focus && initial_focus->GetWidget()) {
|
||||
aura::Window* window = initial_focus->GetWidget()->GetNativeWindow();
|
||||
if (!window || !window->GetRootWindow())
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = views::AccessiblePaneView::SetPaneFocus(initial_focus);
|
||||
|
||||
if (result) {
|
||||
auto children = GetChildrenInZOrder();
|
||||
std::set<ui::KeyboardCode> reg;
|
||||
for (int i = 0, n = children.size(); i < n; ++i) {
|
||||
auto* button = static_cast<SubmenuButton*>(children[i]);
|
||||
bool shifted = false;
|
||||
auto keycode =
|
||||
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
|
||||
|
||||
// We want the menu items to activate if the user presses the accelerator
|
||||
// key, even without alt, since we are now focused on the menu bar
|
||||
if (keycode != ui::VKEY_UNKNOWN && reg.find(keycode) != reg.end()) {
|
||||
reg.insert(keycode);
|
||||
focus_manager()->RegisterAccelerator(
|
||||
ui::Accelerator(keycode, ui::EF_NONE),
|
||||
ui::AcceleratorManager::kNormalPriority, this);
|
||||
}
|
||||
}
|
||||
|
||||
// We want to remove focus / hide menu bar when alt is pressed again
|
||||
focus_manager()->RegisterAccelerator(
|
||||
ui::Accelerator(ui::VKEY_MENU, ui::EF_ALT_DOWN),
|
||||
ui::AcceleratorManager::kNormalPriority, this);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MenuBar::RemovePaneFocus() {
|
||||
views::AccessiblePaneView::RemovePaneFocus();
|
||||
SetAcceleratorVisibility(false);
|
||||
|
||||
auto children = GetChildrenInZOrder();
|
||||
std::set<ui::KeyboardCode> unreg;
|
||||
for (int i = 0, n = children.size(); i < n; ++i) {
|
||||
auto* button = static_cast<SubmenuButton*>(children[i]);
|
||||
bool shifted = false;
|
||||
auto keycode =
|
||||
atom::KeyboardCodeFromCharCode(button->accelerator(), &shifted);
|
||||
|
||||
if (keycode != ui::VKEY_UNKNOWN && unreg.find(keycode) != unreg.end()) {
|
||||
unreg.insert(keycode);
|
||||
focus_manager()->UnregisterAccelerator(
|
||||
ui::Accelerator(keycode, ui::EF_NONE), this);
|
||||
}
|
||||
}
|
||||
|
||||
focus_manager()->UnregisterAccelerator(
|
||||
ui::Accelerator(ui::VKEY_MENU, ui::EF_ALT_DOWN), this);
|
||||
}
|
||||
|
||||
const char* MenuBar::GetClassName() const {
|
||||
return kViewClassName;
|
||||
}
|
||||
|
||||
void MenuBar::OnMenuButtonClicked(views::Button* source,
|
||||
const gfx::Point& point,
|
||||
const ui::Event* event) {
|
||||
// Hide the accelerator when a submenu is activated.
|
||||
SetAcceleratorVisibility(false);
|
||||
|
||||
if (!menu_model_)
|
||||
return;
|
||||
|
||||
if (!window_->HasFocus())
|
||||
window_->RequestFocus();
|
||||
|
||||
int id = source->tag();
|
||||
AtomMenuModel::ItemType type = menu_model_->GetTypeAt(id);
|
||||
if (type != AtomMenuModel::TYPE_SUBMENU) {
|
||||
menu_model_->ActivatedAt(id, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
GetFocusManager()->SetFocusedViewWithReason(
|
||||
source, views::FocusManager::kReasonFocusTraversal);
|
||||
|
||||
// Deleted in MenuDelegate::OnMenuClosed
|
||||
MenuDelegate* menu_delegate = new MenuDelegate(this);
|
||||
menu_delegate->RunMenu(menu_model_->GetSubmenuModelAt(id), source,
|
||||
event != nullptr && event->IsKeyEvent()
|
||||
? ui::MENU_SOURCE_KEYBOARD
|
||||
: ui::MENU_SOURCE_MOUSE);
|
||||
menu_delegate->AddObserver(this);
|
||||
}
|
||||
|
||||
void MenuBar::RefreshColorCache() {
|
||||
const ui::NativeTheme* theme = GetNativeTheme();
|
||||
if (theme) {
|
||||
#if defined(USE_X11)
|
||||
background_color_ = libgtkui::GetBgColor("GtkMenuBar#menubar");
|
||||
enabled_color_ = libgtkui::GetFgColor(
|
||||
"GtkMenuBar#menubar GtkMenuItem#menuitem GtkLabel");
|
||||
disabled_color_ = libgtkui::GetFgColor(
|
||||
"GtkMenuBar#menubar GtkMenuItem#menuitem:disabled GtkLabel");
|
||||
#else
|
||||
background_color_ =
|
||||
theme->GetSystemColor(ui::NativeTheme::kColorId_MenuBackgroundColor);
|
||||
#endif
|
||||
}
|
||||
#if defined(OS_WIN)
|
||||
background_color_ = color_utils::GetSysSkColor(COLOR_MENUBAR);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MenuBar::OnThemeChanged() {
|
||||
RefreshColorCache();
|
||||
UpdateViewColors();
|
||||
}
|
||||
|
||||
void MenuBar::RebuildChildren() {
|
||||
RemoveAllChildViews(true);
|
||||
for (int i = 0, n = GetItemCount(); i < n; ++i) {
|
||||
auto* button =
|
||||
new SubmenuButton(menu_model_->GetLabelAt(i), this, background_color_);
|
||||
button->set_tag(i);
|
||||
AddChildView(button);
|
||||
}
|
||||
UpdateViewColors();
|
||||
}
|
||||
|
||||
void MenuBar::UpdateViewColors() {
|
||||
// set menubar background color
|
||||
SetBackground(views::CreateSolidBackground(background_color_));
|
||||
|
||||
// set child colors
|
||||
if (menu_model_ == nullptr)
|
||||
return;
|
||||
#if defined(USE_X11)
|
||||
const auto& textColor = has_focus_ ? enabled_color_ : disabled_color_;
|
||||
for (auto* child : GetChildrenInZOrder()) {
|
||||
auto* button = static_cast<SubmenuButton*>(child);
|
||||
button->SetTextColor(views::Button::STATE_NORMAL, textColor);
|
||||
button->SetTextColor(views::Button::STATE_DISABLED, disabled_color_);
|
||||
button->SetTextColor(views::Button::STATE_PRESSED, enabled_color_);
|
||||
button->SetTextColor(views::Button::STATE_HOVERED, textColor);
|
||||
button->SetUnderlineColor(textColor);
|
||||
}
|
||||
#elif defined(OS_WIN)
|
||||
for (auto* child : GetChildrenInZOrder()) {
|
||||
auto* button = static_cast<SubmenuButton*>(child);
|
||||
button->SetUnderlineColor(color_utils::GetSysSkColor(COLOR_MENUTEXT));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace atom
|
114
shell/browser/ui/views/menu_bar.h
Normal file
114
shell/browser/ui/views/menu_bar.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "atom/browser/ui/views/menu_delegate.h"
|
||||
#include "atom/browser/ui/views/root_view.h"
|
||||
#include "ui/views/accessible_pane_view.h"
|
||||
#include "ui/views/controls/button/menu_button_listener.h"
|
||||
#include "ui/views/focus/focus_manager.h"
|
||||
#include "ui/views/view.h"
|
||||
|
||||
namespace views {
|
||||
class Button;
|
||||
class MenuButton;
|
||||
} // namespace views
|
||||
|
||||
namespace atom {
|
||||
|
||||
class MenuBarColorUpdater : public views::FocusChangeListener {
|
||||
public:
|
||||
explicit MenuBarColorUpdater(MenuBar* menu_bar);
|
||||
~MenuBarColorUpdater() override;
|
||||
|
||||
void OnDidChangeFocus(views::View* focused_before,
|
||||
views::View* focused_now) override;
|
||||
void OnWillChangeFocus(views::View* focused_before,
|
||||
views::View* focused_now) override {}
|
||||
|
||||
private:
|
||||
MenuBar* menu_bar_;
|
||||
};
|
||||
|
||||
class MenuBar : public views::AccessiblePaneView,
|
||||
public views::MenuButtonListener,
|
||||
public atom::MenuDelegate::Observer {
|
||||
public:
|
||||
static const char kViewClassName[];
|
||||
|
||||
explicit MenuBar(RootView* window);
|
||||
~MenuBar() override;
|
||||
|
||||
// Replaces current menu with a new one.
|
||||
void SetMenu(AtomMenuModel* menu_model);
|
||||
|
||||
// Shows underline under accelerators.
|
||||
void SetAcceleratorVisibility(bool visible);
|
||||
|
||||
// Returns true if the submenu has accelerator |key|
|
||||
bool HasAccelerator(base::char16 key);
|
||||
|
||||
// Shows the submenu whose accelerator is |key|.
|
||||
void ActivateAccelerator(base::char16 key);
|
||||
|
||||
// Returns there are how many items in the root menu.
|
||||
int GetItemCount() const;
|
||||
|
||||
// Get the menu under specified screen point.
|
||||
bool GetMenuButtonFromScreenPoint(const gfx::Point& point,
|
||||
AtomMenuModel** menu_model,
|
||||
views::MenuButton** button);
|
||||
|
||||
// atom::MenuDelegate::Observer:
|
||||
void OnBeforeExecuteCommand() override;
|
||||
void OnMenuClosed() override;
|
||||
|
||||
// views::AccessiblePaneView:
|
||||
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
|
||||
bool SetPaneFocus(views::View* initial_focus) override;
|
||||
void RemovePaneFocus() override;
|
||||
|
||||
protected:
|
||||
// views::View:
|
||||
const char* GetClassName() const override;
|
||||
|
||||
// views::MenuButtonListener:
|
||||
void OnMenuButtonClicked(views::Button* source,
|
||||
const gfx::Point& point,
|
||||
const ui::Event* event) override;
|
||||
void OnThemeChanged() override;
|
||||
|
||||
private:
|
||||
friend class MenuBarColorUpdater;
|
||||
|
||||
void RebuildChildren();
|
||||
void UpdateViewColors();
|
||||
|
||||
void RefreshColorCache();
|
||||
SkColor background_color_;
|
||||
#if defined(USE_X11)
|
||||
SkColor enabled_color_;
|
||||
SkColor disabled_color_;
|
||||
#endif
|
||||
|
||||
RootView* window_ = nullptr;
|
||||
AtomMenuModel* menu_model_ = nullptr;
|
||||
|
||||
View* FindAccelChild(base::char16 key);
|
||||
|
||||
bool has_focus_ = true;
|
||||
|
||||
std::unique_ptr<MenuBarColorUpdater> color_updater_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MenuBar);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_
|
143
shell/browser/ui/views/menu_delegate.cc
Normal file
143
shell/browser/ui/views/menu_delegate.cc
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/menu_delegate.h"
|
||||
|
||||
#include "atom/browser/ui/views/menu_bar.h"
|
||||
#include "atom/browser/ui/views/menu_model_adapter.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "ui/views/controls/button/menu_button.h"
|
||||
#include "ui/views/controls/menu/menu_item_view.h"
|
||||
#include "ui/views/controls/menu/menu_runner.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
MenuDelegate::MenuDelegate(MenuBar* menu_bar)
|
||||
: menu_bar_(menu_bar), id_(-1), hold_first_switch_(false) {}
|
||||
|
||||
MenuDelegate::~MenuDelegate() {}
|
||||
|
||||
void MenuDelegate::RunMenu(AtomMenuModel* model,
|
||||
views::Button* button,
|
||||
ui::MenuSourceType source_type) {
|
||||
gfx::Point screen_loc;
|
||||
views::View::ConvertPointToScreen(button, &screen_loc);
|
||||
// Subtract 1 from the height to make the popup flush with the button border.
|
||||
gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(),
|
||||
button->height() - 1);
|
||||
|
||||
if (source_type == ui::MENU_SOURCE_KEYBOARD) {
|
||||
hold_first_switch_ = true;
|
||||
}
|
||||
|
||||
id_ = button->tag();
|
||||
adapter_.reset(new MenuModelAdapter(model));
|
||||
|
||||
views::MenuItemView* item = new views::MenuItemView(this);
|
||||
static_cast<MenuModelAdapter*>(adapter_.get())->BuildMenu(item);
|
||||
|
||||
menu_runner_.reset(new views::MenuRunner(
|
||||
item,
|
||||
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS));
|
||||
menu_runner_->RunMenuAt(
|
||||
button->GetWidget()->GetTopLevelWidget(),
|
||||
static_cast<views::MenuButton*>(button)->button_controller(), bounds,
|
||||
views::MenuAnchorPosition::kTopRight, source_type);
|
||||
}
|
||||
|
||||
void MenuDelegate::ExecuteCommand(int id) {
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnBeforeExecuteCommand();
|
||||
adapter_->ExecuteCommand(id);
|
||||
}
|
||||
|
||||
void MenuDelegate::ExecuteCommand(int id, int mouse_event_flags) {
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnBeforeExecuteCommand();
|
||||
adapter_->ExecuteCommand(id, mouse_event_flags);
|
||||
}
|
||||
|
||||
bool MenuDelegate::IsTriggerableEvent(views::MenuItemView* source,
|
||||
const ui::Event& e) {
|
||||
return adapter_->IsTriggerableEvent(source, e);
|
||||
}
|
||||
|
||||
bool MenuDelegate::GetAccelerator(int id, ui::Accelerator* accelerator) const {
|
||||
return adapter_->GetAccelerator(id, accelerator);
|
||||
}
|
||||
|
||||
base::string16 MenuDelegate::GetLabel(int id) const {
|
||||
return adapter_->GetLabel(id);
|
||||
}
|
||||
|
||||
void MenuDelegate::GetLabelStyle(int id, LabelStyle* style) const {
|
||||
return adapter_->GetLabelStyle(id, style);
|
||||
}
|
||||
|
||||
bool MenuDelegate::IsCommandEnabled(int id) const {
|
||||
return adapter_->IsCommandEnabled(id);
|
||||
}
|
||||
|
||||
bool MenuDelegate::IsCommandVisible(int id) const {
|
||||
return adapter_->IsCommandVisible(id);
|
||||
}
|
||||
|
||||
bool MenuDelegate::IsItemChecked(int id) const {
|
||||
return adapter_->IsItemChecked(id);
|
||||
}
|
||||
|
||||
void MenuDelegate::WillShowMenu(views::MenuItemView* menu) {
|
||||
adapter_->WillShowMenu(menu);
|
||||
}
|
||||
|
||||
void MenuDelegate::WillHideMenu(views::MenuItemView* menu) {
|
||||
adapter_->WillHideMenu(menu);
|
||||
}
|
||||
|
||||
void MenuDelegate::OnMenuClosed(views::MenuItemView* menu) {
|
||||
for (Observer& obs : observers_)
|
||||
obs.OnMenuClosed();
|
||||
|
||||
// Only switch to new menu when current menu is closed.
|
||||
if (button_to_open_)
|
||||
button_to_open_->Activate(nullptr);
|
||||
delete this;
|
||||
}
|
||||
|
||||
views::MenuItemView* MenuDelegate::GetSiblingMenu(
|
||||
views::MenuItemView* menu,
|
||||
const gfx::Point& screen_point,
|
||||
views::MenuAnchorPosition* anchor,
|
||||
bool* has_mnemonics,
|
||||
views::MenuButton**) {
|
||||
if (hold_first_switch_) {
|
||||
hold_first_switch_ = false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO(zcbenz): We should follow Chromium's logics on implementing the
|
||||
// sibling menu switches, this code is almost a hack.
|
||||
views::MenuButton* button;
|
||||
AtomMenuModel* model;
|
||||
if (menu_bar_->GetMenuButtonFromScreenPoint(screen_point, &model, &button) &&
|
||||
button->tag() != id_) {
|
||||
bool switch_in_progress = !!button_to_open_;
|
||||
// Always update target to open.
|
||||
button_to_open_ = button;
|
||||
// Switching menu asyncnously to avoid crash.
|
||||
if (!switch_in_progress) {
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {content::BrowserThread::UI},
|
||||
base::BindOnce(&views::MenuRunner::Cancel,
|
||||
base::Unretained(menu_runner_.get())));
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace atom
|
80
shell/browser/ui/views/menu_delegate.h
Normal file
80
shell/browser/ui/views/menu_delegate.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "ui/views/controls/menu/menu_delegate.h"
|
||||
|
||||
namespace views {
|
||||
class MenuRunner;
|
||||
class Button;
|
||||
} // namespace views
|
||||
|
||||
namespace atom {
|
||||
|
||||
class MenuBar;
|
||||
|
||||
class MenuDelegate : public views::MenuDelegate {
|
||||
public:
|
||||
explicit MenuDelegate(MenuBar* menu_bar);
|
||||
~MenuDelegate() override;
|
||||
|
||||
void RunMenu(AtomMenuModel* model,
|
||||
views::Button* button,
|
||||
ui::MenuSourceType source_type);
|
||||
|
||||
class Observer {
|
||||
public:
|
||||
virtual void OnBeforeExecuteCommand() = 0;
|
||||
virtual void OnMenuClosed() = 0;
|
||||
};
|
||||
|
||||
void AddObserver(Observer* obs) { observers_.AddObserver(obs); }
|
||||
|
||||
void RemoveObserver(const Observer* obs) { observers_.RemoveObserver(obs); }
|
||||
|
||||
protected:
|
||||
// views::MenuDelegate:
|
||||
void ExecuteCommand(int id) override;
|
||||
void ExecuteCommand(int id, int mouse_event_flags) override;
|
||||
bool IsTriggerableEvent(views::MenuItemView* source,
|
||||
const ui::Event& e) override;
|
||||
bool GetAccelerator(int id, ui::Accelerator* accelerator) const override;
|
||||
base::string16 GetLabel(int id) const override;
|
||||
void GetLabelStyle(int id, LabelStyle* style) const override;
|
||||
bool IsCommandEnabled(int id) const override;
|
||||
bool IsCommandVisible(int id) const override;
|
||||
bool IsItemChecked(int id) const override;
|
||||
void WillShowMenu(views::MenuItemView* menu) override;
|
||||
void WillHideMenu(views::MenuItemView* menu) override;
|
||||
void OnMenuClosed(views::MenuItemView* menu) override;
|
||||
views::MenuItemView* GetSiblingMenu(views::MenuItemView* menu,
|
||||
const gfx::Point& screen_point,
|
||||
views::MenuAnchorPosition* anchor,
|
||||
bool* has_mnemonics,
|
||||
views::MenuButton** button) override;
|
||||
|
||||
private:
|
||||
MenuBar* menu_bar_;
|
||||
int id_;
|
||||
std::unique_ptr<views::MenuDelegate> adapter_;
|
||||
std::unique_ptr<views::MenuRunner> menu_runner_;
|
||||
|
||||
// The menu button to switch to.
|
||||
views::MenuButton* button_to_open_ = nullptr;
|
||||
bool hold_first_switch_;
|
||||
|
||||
base::ObserverList<Observer>::Unchecked observers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MenuDelegate);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_
|
25
shell/browser/ui/views/menu_model_adapter.cc
Normal file
25
shell/browser/ui/views/menu_model_adapter.cc
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/menu_model_adapter.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
MenuModelAdapter::MenuModelAdapter(AtomMenuModel* menu_model)
|
||||
: views::MenuModelAdapter(menu_model), menu_model_(menu_model) {}
|
||||
|
||||
MenuModelAdapter::~MenuModelAdapter() {}
|
||||
|
||||
bool MenuModelAdapter::GetAccelerator(int id,
|
||||
ui::Accelerator* accelerator) const {
|
||||
ui::MenuModel* model = menu_model_;
|
||||
int index = 0;
|
||||
if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) {
|
||||
return static_cast<AtomMenuModel*>(model)->GetAcceleratorAtWithParams(
|
||||
index, true, accelerator);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace atom
|
29
shell/browser/ui/views/menu_model_adapter.h
Normal file
29
shell/browser/ui/views/menu_model_adapter.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2016 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_MENU_MODEL_ADAPTER_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_MENU_MODEL_ADAPTER_H_
|
||||
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "ui/views/controls/menu/menu_model_adapter.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class MenuModelAdapter : public views::MenuModelAdapter {
|
||||
public:
|
||||
explicit MenuModelAdapter(AtomMenuModel* menu_model);
|
||||
~MenuModelAdapter() override;
|
||||
|
||||
protected:
|
||||
bool GetAccelerator(int id, ui::Accelerator* accelerator) const override;
|
||||
|
||||
private:
|
||||
AtomMenuModel* menu_model_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MenuModelAdapter);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_MENU_MODEL_ADAPTER_H_
|
28
shell/browser/ui/views/native_frame_view.cc
Normal file
28
shell/browser/ui/views/native_frame_view.cc
Normal file
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) 2015 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/native_frame_view.h"
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
const char NativeFrameView::kViewClassName[] = "AtomNativeFrameView";
|
||||
|
||||
NativeFrameView::NativeFrameView(NativeWindow* window, views::Widget* widget)
|
||||
: views::NativeFrameView(widget), window_(window) {}
|
||||
|
||||
gfx::Size NativeFrameView::GetMinimumSize() const {
|
||||
return window_->GetMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size NativeFrameView::GetMaximumSize() const {
|
||||
return window_->GetMaximumSize();
|
||||
}
|
||||
|
||||
const char* NativeFrameView::GetClassName() const {
|
||||
return kViewClassName;
|
||||
}
|
||||
|
||||
} // namespace atom
|
35
shell/browser/ui/views/native_frame_view.h
Normal file
35
shell/browser/ui/views/native_frame_view.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2015 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_
|
||||
|
||||
#include "ui/views/window/native_frame_view.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
// Like the views::NativeFrameView, but returns the min/max size from the
|
||||
// NativeWindowViews.
|
||||
class NativeFrameView : public views::NativeFrameView {
|
||||
public:
|
||||
static const char kViewClassName[];
|
||||
NativeFrameView(NativeWindow* window, views::Widget* widget);
|
||||
|
||||
protected:
|
||||
// views::View:
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
const char* GetClassName() const override;
|
||||
|
||||
private:
|
||||
NativeWindow* window_; // weak ref.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeFrameView);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_H_
|
233
shell/browser/ui/views/root_view.cc
Normal file
233
shell/browser/ui/views/root_view.cc
Normal file
|
@ -0,0 +1,233 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/root_view.h"
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/views/menu_bar.h"
|
||||
#include "content/public/browser/native_web_keyboard_event.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// The menu bar height in pixels.
|
||||
#if defined(OS_WIN)
|
||||
const int kMenuBarHeight = 20;
|
||||
#else
|
||||
const int kMenuBarHeight = 25;
|
||||
#endif
|
||||
|
||||
bool IsAltKey(const content::NativeWebKeyboardEvent& event) {
|
||||
return event.windows_key_code == ui::VKEY_MENU;
|
||||
}
|
||||
|
||||
bool IsAltModifier(const content::NativeWebKeyboardEvent& event) {
|
||||
typedef content::NativeWebKeyboardEvent::Modifiers Modifiers;
|
||||
int modifiers = event.GetModifiers();
|
||||
modifiers &= ~Modifiers::kNumLockOn;
|
||||
modifiers &= ~Modifiers::kCapsLockOn;
|
||||
return (modifiers == Modifiers::kAltKey) ||
|
||||
(modifiers == (Modifiers::kAltKey | Modifiers::kIsLeft)) ||
|
||||
(modifiers == (Modifiers::kAltKey | Modifiers::kIsRight));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RootView::RootView(NativeWindow* window)
|
||||
: window_(window),
|
||||
last_focused_view_tracker_(std::make_unique<views::ViewTracker>()) {
|
||||
set_owned_by_client();
|
||||
}
|
||||
|
||||
RootView::~RootView() {}
|
||||
|
||||
void RootView::SetMenu(AtomMenuModel* menu_model) {
|
||||
if (menu_model == nullptr) {
|
||||
// Remove accelerators
|
||||
UnregisterAcceleratorsWithFocusManager();
|
||||
// and menu bar.
|
||||
SetMenuBarVisibility(false);
|
||||
menu_bar_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterAcceleratorsWithFocusManager(menu_model);
|
||||
|
||||
// Do not show menu bar in frameless window.
|
||||
if (!window_->has_frame())
|
||||
return;
|
||||
|
||||
if (!menu_bar_) {
|
||||
menu_bar_.reset(new MenuBar(this));
|
||||
menu_bar_->set_owned_by_client();
|
||||
if (!menu_bar_autohide_)
|
||||
SetMenuBarVisibility(true);
|
||||
}
|
||||
|
||||
menu_bar_->SetMenu(menu_model);
|
||||
Layout();
|
||||
}
|
||||
|
||||
bool RootView::HasMenu() const {
|
||||
return !!menu_bar_;
|
||||
}
|
||||
|
||||
int RootView::GetMenuBarHeight() const {
|
||||
return kMenuBarHeight;
|
||||
}
|
||||
|
||||
void RootView::SetAutoHideMenuBar(bool auto_hide) {
|
||||
menu_bar_autohide_ = auto_hide;
|
||||
}
|
||||
|
||||
bool RootView::IsMenuBarAutoHide() const {
|
||||
return menu_bar_autohide_;
|
||||
}
|
||||
|
||||
void RootView::SetMenuBarVisibility(bool visible) {
|
||||
if (!window_->content_view() || !menu_bar_ || menu_bar_visible_ == visible)
|
||||
return;
|
||||
|
||||
menu_bar_visible_ = visible;
|
||||
if (visible) {
|
||||
DCHECK_EQ(children().size(), 1ul);
|
||||
AddChildView(menu_bar_.get());
|
||||
} else {
|
||||
DCHECK_EQ(children().size(), 2ul);
|
||||
RemoveChildView(menu_bar_.get());
|
||||
}
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
bool RootView::IsMenuBarVisible() const {
|
||||
return menu_bar_visible_;
|
||||
}
|
||||
|
||||
void RootView::HandleKeyEvent(const content::NativeWebKeyboardEvent& event) {
|
||||
if (!menu_bar_)
|
||||
return;
|
||||
|
||||
// Show accelerator when "Alt" is pressed.
|
||||
if (menu_bar_visible_ && IsAltKey(event))
|
||||
menu_bar_->SetAcceleratorVisibility(event.GetType() ==
|
||||
blink::WebInputEvent::kRawKeyDown);
|
||||
|
||||
// Show the submenu when "Alt+Key" is pressed.
|
||||
if (event.GetType() == blink::WebInputEvent::kRawKeyDown &&
|
||||
!IsAltKey(event) && IsAltModifier(event)) {
|
||||
if (menu_bar_->HasAccelerator(event.windows_key_code)) {
|
||||
if (!menu_bar_visible_) {
|
||||
SetMenuBarVisibility(true);
|
||||
|
||||
View* focused_view = GetFocusManager()->GetFocusedView();
|
||||
last_focused_view_tracker_->SetView(focused_view);
|
||||
menu_bar_->RequestFocus();
|
||||
}
|
||||
|
||||
menu_bar_->ActivateAccelerator(event.windows_key_code);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the menu bar only when a single Alt is released.
|
||||
if (event.GetType() == blink::WebInputEvent::kRawKeyDown && IsAltKey(event)) {
|
||||
// When a single Alt is pressed:
|
||||
menu_bar_alt_pressed_ = true;
|
||||
} else if (event.GetType() == blink::WebInputEvent::kKeyUp &&
|
||||
IsAltKey(event) && menu_bar_alt_pressed_) {
|
||||
// When a single Alt is released right after a Alt is pressed:
|
||||
menu_bar_alt_pressed_ = false;
|
||||
if (menu_bar_autohide_)
|
||||
SetMenuBarVisibility(!menu_bar_visible_);
|
||||
|
||||
View* focused_view = GetFocusManager()->GetFocusedView();
|
||||
last_focused_view_tracker_->SetView(focused_view);
|
||||
if (menu_bar_visible_) {
|
||||
menu_bar_->RequestFocus();
|
||||
// Show accelerators when menu bar is focused
|
||||
menu_bar_->SetAcceleratorVisibility(true);
|
||||
}
|
||||
} else {
|
||||
// When any other keys except single Alt have been pressed/released:
|
||||
menu_bar_alt_pressed_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RootView::RestoreFocus() {
|
||||
View* last_focused_view = last_focused_view_tracker_->view();
|
||||
if (last_focused_view) {
|
||||
GetFocusManager()->SetFocusedViewWithReason(
|
||||
last_focused_view, views::FocusManager::kReasonFocusRestore);
|
||||
}
|
||||
if (menu_bar_autohide_)
|
||||
SetMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
void RootView::ResetAltState() {
|
||||
menu_bar_alt_pressed_ = false;
|
||||
}
|
||||
|
||||
void RootView::Layout() {
|
||||
if (!window_->content_view()) // Not ready yet.
|
||||
return;
|
||||
|
||||
const auto menu_bar_bounds =
|
||||
menu_bar_visible_
|
||||
? gfx::Rect(insets_.left(), insets_.top(),
|
||||
size().width() - insets_.width(), kMenuBarHeight)
|
||||
: gfx::Rect();
|
||||
if (menu_bar_)
|
||||
menu_bar_->SetBoundsRect(menu_bar_bounds);
|
||||
|
||||
window_->content_view()->SetBoundsRect(
|
||||
gfx::Rect(insets_.left(),
|
||||
menu_bar_visible_ ? menu_bar_bounds.bottom() : insets_.top(),
|
||||
size().width() - insets_.width(),
|
||||
size().height() - menu_bar_bounds.height() - insets_.height()));
|
||||
}
|
||||
|
||||
gfx::Size RootView::GetMinimumSize() const {
|
||||
return window_->GetMinimumSize();
|
||||
}
|
||||
|
||||
gfx::Size RootView::GetMaximumSize() const {
|
||||
return window_->GetMaximumSize();
|
||||
}
|
||||
|
||||
bool RootView::AcceleratorPressed(const ui::Accelerator& accelerator) {
|
||||
return accelerator_util::TriggerAcceleratorTableCommand(&accelerator_table_,
|
||||
accelerator);
|
||||
}
|
||||
|
||||
void RootView::RegisterAcceleratorsWithFocusManager(AtomMenuModel* menu_model) {
|
||||
if (!menu_model)
|
||||
return;
|
||||
// Clear previous accelerators.
|
||||
UnregisterAcceleratorsWithFocusManager();
|
||||
|
||||
views::FocusManager* focus_manager = GetFocusManager();
|
||||
// Register accelerators with focus manager.
|
||||
accelerator_util::GenerateAcceleratorTable(&accelerator_table_, menu_model);
|
||||
for (const auto& iter : accelerator_table_) {
|
||||
focus_manager->RegisterAccelerator(
|
||||
iter.first, ui::AcceleratorManager::kNormalPriority, this);
|
||||
}
|
||||
}
|
||||
|
||||
void RootView::UnregisterAcceleratorsWithFocusManager() {
|
||||
views::FocusManager* focus_manager = GetFocusManager();
|
||||
accelerator_table_.clear();
|
||||
focus_manager->UnregisterAccelerators(this);
|
||||
}
|
||||
|
||||
void RootView::SetInsets(const gfx::Insets& insets) {
|
||||
if (insets != insets_) {
|
||||
insets_ = insets;
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace atom
|
74
shell/browser/ui/views/root_view.h
Normal file
74
shell/browser/ui/views/root_view.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) 2018 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_ROOT_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_ROOT_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "atom/browser/ui/accelerator_util.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/views/view.h"
|
||||
#include "ui/views/view_tracker.h"
|
||||
|
||||
namespace content {
|
||||
struct NativeWebKeyboardEvent;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class AtomMenuModel;
|
||||
class MenuBar;
|
||||
class NativeWindow;
|
||||
|
||||
class RootView : public views::View {
|
||||
public:
|
||||
explicit RootView(NativeWindow* window);
|
||||
~RootView() override;
|
||||
|
||||
void SetMenu(AtomMenuModel* menu_model);
|
||||
bool HasMenu() const;
|
||||
int GetMenuBarHeight() const;
|
||||
void SetAutoHideMenuBar(bool auto_hide);
|
||||
bool IsMenuBarAutoHide() const;
|
||||
void SetMenuBarVisibility(bool visible);
|
||||
bool IsMenuBarVisible() const;
|
||||
void HandleKeyEvent(const content::NativeWebKeyboardEvent& event);
|
||||
void ResetAltState();
|
||||
void RestoreFocus();
|
||||
// Register/Unregister accelerators supported by the menu model.
|
||||
void RegisterAcceleratorsWithFocusManager(AtomMenuModel* menu_model);
|
||||
void UnregisterAcceleratorsWithFocusManager();
|
||||
void SetInsets(const gfx::Insets& insets);
|
||||
gfx::Insets insets() const { return insets_; }
|
||||
|
||||
// views::View:
|
||||
void Layout() override;
|
||||
gfx::Size GetMinimumSize() const override;
|
||||
gfx::Size GetMaximumSize() const override;
|
||||
bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
|
||||
|
||||
private:
|
||||
// Parent window, weak ref.
|
||||
NativeWindow* window_;
|
||||
|
||||
// Menu bar.
|
||||
std::unique_ptr<MenuBar> menu_bar_;
|
||||
bool menu_bar_autohide_ = false;
|
||||
bool menu_bar_visible_ = false;
|
||||
bool menu_bar_alt_pressed_ = false;
|
||||
|
||||
gfx::Insets insets_;
|
||||
|
||||
// Map from accelerator to menu item's command id.
|
||||
accelerator_util::AcceleratorTable accelerator_table_;
|
||||
|
||||
std::unique_ptr<views::ViewTracker> last_focused_view_tracker_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(RootView);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_ROOT_VIEW_H_
|
116
shell/browser/ui/views/submenu_button.cc
Normal file
116
shell/browser/ui/views/submenu_button.cc
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/submenu_button.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/color_utils.h"
|
||||
#include "ui/gfx/text_utils.h"
|
||||
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
|
||||
#include "ui/views/animation/ink_drop_host_view.h"
|
||||
#include "ui/views/animation/ink_drop_impl.h"
|
||||
#include "ui/views/controls/button/label_button_border.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
SubmenuButton::SubmenuButton(const base::string16& title,
|
||||
views::MenuButtonListener* menu_button_listener,
|
||||
const SkColor& background_color)
|
||||
: views::MenuButton(gfx::RemoveAcceleratorChar(title, '&', NULL, NULL),
|
||||
menu_button_listener),
|
||||
background_color_(background_color) {
|
||||
#if defined(OS_LINUX)
|
||||
// Dont' use native style border.
|
||||
SetBorder(CreateDefaultBorder());
|
||||
#endif
|
||||
|
||||
if (GetUnderlinePosition(title, &accelerator_, &underline_start_,
|
||||
&underline_end_))
|
||||
gfx::Canvas::SizeStringInt(GetText(), gfx::FontList(), &text_width_,
|
||||
&text_height_, 0, 0);
|
||||
|
||||
SetInkDropMode(InkDropMode::ON);
|
||||
set_ink_drop_base_color(
|
||||
color_utils::BlendTowardMaxContrast(background_color_, 0x81));
|
||||
}
|
||||
|
||||
SubmenuButton::~SubmenuButton() {}
|
||||
|
||||
std::unique_ptr<views::InkDropRipple> SubmenuButton::CreateInkDropRipple()
|
||||
const {
|
||||
std::unique_ptr<views::InkDropRipple> ripple(
|
||||
new views::FloodFillInkDropRipple(
|
||||
size(), GetInkDropCenterBasedOnLastEvent(), GetInkDropBaseColor(),
|
||||
ink_drop_visible_opacity()));
|
||||
return ripple;
|
||||
}
|
||||
|
||||
std::unique_ptr<views::InkDrop> SubmenuButton::CreateInkDrop() {
|
||||
std::unique_ptr<views::InkDropImpl> ink_drop =
|
||||
views::Button::CreateDefaultInkDropImpl();
|
||||
ink_drop->SetShowHighlightOnHover(false);
|
||||
ink_drop->SetShowHighlightOnFocus(true);
|
||||
return std::move(ink_drop);
|
||||
}
|
||||
|
||||
void SubmenuButton::SetAcceleratorVisibility(bool visible) {
|
||||
if (visible == show_underline_)
|
||||
return;
|
||||
|
||||
show_underline_ = visible;
|
||||
SchedulePaint();
|
||||
}
|
||||
|
||||
void SubmenuButton::SetUnderlineColor(SkColor color) {
|
||||
underline_color_ = color;
|
||||
}
|
||||
|
||||
void SubmenuButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
|
||||
node_data->SetName(GetAccessibleName());
|
||||
node_data->role = ax::mojom::Role::kPopUpButton;
|
||||
}
|
||||
|
||||
void SubmenuButton::PaintButtonContents(gfx::Canvas* canvas) {
|
||||
views::MenuButton::PaintButtonContents(canvas);
|
||||
|
||||
if (show_underline_ && (underline_start_ != underline_end_)) {
|
||||
float padding = (width() - text_width_) / 2;
|
||||
float underline_height = (height() + text_height_) / 2 - 2;
|
||||
canvas->DrawSharpLine(
|
||||
gfx::PointF(underline_start_ + padding, underline_height),
|
||||
gfx::PointF(underline_end_ + padding, underline_height),
|
||||
underline_color_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SubmenuButton::GetUnderlinePosition(const base::string16& text,
|
||||
base::char16* accelerator,
|
||||
int* start,
|
||||
int* end) const {
|
||||
int pos, span;
|
||||
base::string16 trimmed = gfx::RemoveAcceleratorChar(text, '&', &pos, &span);
|
||||
if (pos > -1 && span != 0) {
|
||||
*accelerator = base::ToUpperASCII(trimmed[pos]);
|
||||
GetCharacterPosition(trimmed, pos, start);
|
||||
GetCharacterPosition(trimmed, pos + span, end);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SubmenuButton::GetCharacterPosition(const base::string16& text,
|
||||
int index,
|
||||
int* pos) const {
|
||||
int height = 0;
|
||||
gfx::Canvas::SizeStringInt(text.substr(0, index), gfx::FontList(), pos,
|
||||
&height, 0, 0);
|
||||
}
|
||||
|
||||
} // namespace atom
|
63
shell/browser/ui/views/submenu_button.h
Normal file
63
shell/browser/ui/views/submenu_button.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_SUBMENU_BUTTON_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_SUBMENU_BUTTON_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ui/accessibility/ax_node_data.h"
|
||||
#include "ui/views/animation/ink_drop_highlight.h"
|
||||
#include "ui/views/controls/button/menu_button.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
// Special button that used by menu bar to show submenus.
|
||||
class SubmenuButton : public views::MenuButton {
|
||||
public:
|
||||
SubmenuButton(const base::string16& title,
|
||||
views::MenuButtonListener* menu_button_listener,
|
||||
const SkColor& background_color);
|
||||
~SubmenuButton() override;
|
||||
|
||||
void SetAcceleratorVisibility(bool visible);
|
||||
void SetUnderlineColor(SkColor color);
|
||||
|
||||
base::char16 accelerator() const { return accelerator_; }
|
||||
|
||||
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
|
||||
|
||||
// views::MenuButton:
|
||||
void PaintButtonContents(gfx::Canvas* canvas) override;
|
||||
|
||||
// views::InkDropHostView:
|
||||
std::unique_ptr<views::InkDropRipple> CreateInkDropRipple() const override;
|
||||
std::unique_ptr<views::InkDrop> CreateInkDrop() override;
|
||||
|
||||
private:
|
||||
bool GetUnderlinePosition(const base::string16& text,
|
||||
base::char16* accelerator,
|
||||
int* start,
|
||||
int* end) const;
|
||||
void GetCharacterPosition(const base::string16& text,
|
||||
int index,
|
||||
int* pos) const;
|
||||
|
||||
base::char16 accelerator_ = 0;
|
||||
|
||||
bool show_underline_ = false;
|
||||
|
||||
int underline_start_ = 0;
|
||||
int underline_end_ = 0;
|
||||
int text_width_ = 0;
|
||||
int text_height_ = 0;
|
||||
SkColor underline_color_ = SK_ColorBLACK;
|
||||
SkColor background_color_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SubmenuButton);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_SUBMENU_BUTTON_H_
|
37
shell/browser/ui/views/win_frame_view.cc
Normal file
37
shell/browser/ui/views/win_frame_view.cc
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/views/win_frame_view.h"
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/win/hwnd_util.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
const char WinFrameView::kViewClassName[] = "WinFrameView";
|
||||
|
||||
WinFrameView::WinFrameView() {}
|
||||
|
||||
WinFrameView::~WinFrameView() {}
|
||||
|
||||
gfx::Rect WinFrameView::GetWindowBoundsForClientBounds(
|
||||
const gfx::Rect& client_bounds) const {
|
||||
return views::GetWindowBoundsForClientBounds(
|
||||
static_cast<views::View*>(const_cast<WinFrameView*>(this)),
|
||||
client_bounds);
|
||||
}
|
||||
|
||||
int WinFrameView::NonClientHitTest(const gfx::Point& point) {
|
||||
if (window_->has_frame())
|
||||
return frame_->client_view()->NonClientHitTest(point);
|
||||
else
|
||||
return FramelessView::NonClientHitTest(point);
|
||||
}
|
||||
|
||||
const char* WinFrameView::GetClassName() const {
|
||||
return kViewClassName;
|
||||
}
|
||||
|
||||
} // namespace atom
|
32
shell/browser/ui/views/win_frame_view.h
Normal file
32
shell/browser/ui/views/win_frame_view.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2014 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
|
||||
|
||||
#include "atom/browser/ui/views/frameless_view.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class WinFrameView : public FramelessView {
|
||||
public:
|
||||
static const char kViewClassName[];
|
||||
WinFrameView();
|
||||
~WinFrameView() override;
|
||||
|
||||
// views::NonClientFrameView:
|
||||
gfx::Rect GetWindowBoundsForClientBounds(
|
||||
const gfx::Rect& client_bounds) const override;
|
||||
int NonClientHitTest(const gfx::Point& point) override;
|
||||
|
||||
// views::View:
|
||||
const char* GetClassName() const override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(WinFrameView);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
|
231
shell/browser/ui/webui/pdf_viewer_handler.cc
Normal file
231
shell/browser/ui/webui/pdf_viewer_handler.cc
Normal file
|
@ -0,0 +1,231 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/webui/pdf_viewer_handler.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/common/atom_constants.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "content/public/browser/stream_handle.h"
|
||||
#include "content/public/browser/stream_info.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_ui.h"
|
||||
#include "content/public/common/page_zoom.h"
|
||||
#include "content/public/common/url_constants.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/webui/web_ui_util.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
void CreateResponseHeadersDictionary(const net::HttpResponseHeaders* headers,
|
||||
base::DictionaryValue* result) {
|
||||
if (!headers)
|
||||
return;
|
||||
|
||||
size_t iter = 0;
|
||||
std::string header_name;
|
||||
std::string header_value;
|
||||
while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
|
||||
base::Value* existing_value = nullptr;
|
||||
if (result->Get(header_name, &existing_value)) {
|
||||
std::string src = existing_value->GetString();
|
||||
result->SetString(header_name, src + ", " + header_value);
|
||||
} else {
|
||||
result->SetString(header_name, header_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateStreamInfo(base::DictionaryValue* stream_info,
|
||||
content::StreamInfo* stream,
|
||||
const std::string& original_url) {
|
||||
auto headers_dict = std::make_unique<base::DictionaryValue>();
|
||||
auto stream_url = stream->handle->GetURL().spec();
|
||||
CreateResponseHeadersDictionary(stream->response_headers.get(),
|
||||
headers_dict.get());
|
||||
stream_info->SetString("streamURL", stream_url);
|
||||
stream_info->SetString("originalURL", original_url);
|
||||
stream_info->Set("responseHeaders", std::move(headers_dict));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PdfViewerHandler::PdfViewerHandler(const std::string& src)
|
||||
: original_url_(src) {}
|
||||
|
||||
PdfViewerHandler::~PdfViewerHandler() {
|
||||
RemoveObserver();
|
||||
}
|
||||
|
||||
void PdfViewerHandler::SetPdfResourceStream(content::StreamInfo* stream) {
|
||||
stream_ = stream;
|
||||
if (!!initialize_callback_id_.get()) {
|
||||
auto list = std::make_unique<base::ListValue>();
|
||||
list->Set(0, std::move(initialize_callback_id_));
|
||||
Initialize(list.get());
|
||||
}
|
||||
}
|
||||
|
||||
void PdfViewerHandler::RegisterMessages() {
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"initialize", base::BindRepeating(&PdfViewerHandler::Initialize,
|
||||
base::Unretained(this)));
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"getDefaultZoom", base::BindRepeating(&PdfViewerHandler::GetInitialZoom,
|
||||
base::Unretained(this)));
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"getInitialZoom", base::BindRepeating(&PdfViewerHandler::GetInitialZoom,
|
||||
base::Unretained(this)));
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"setZoom",
|
||||
base::BindRepeating(&PdfViewerHandler::SetZoom, base::Unretained(this)));
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"getStrings", base::BindRepeating(&PdfViewerHandler::GetStrings,
|
||||
base::Unretained(this)));
|
||||
web_ui()->RegisterMessageCallback(
|
||||
"reload",
|
||||
base::BindRepeating(&PdfViewerHandler::Reload, base::Unretained(this)));
|
||||
}
|
||||
|
||||
void PdfViewerHandler::OnJavascriptAllowed() {
|
||||
AddObserver();
|
||||
}
|
||||
|
||||
void PdfViewerHandler::OnJavascriptDisallowed() {
|
||||
RemoveObserver();
|
||||
}
|
||||
|
||||
void PdfViewerHandler::Initialize(const base::ListValue* args) {
|
||||
CHECK_EQ(1U, args->GetSize());
|
||||
const base::Value* callback_id;
|
||||
CHECK(args->Get(0, &callback_id));
|
||||
|
||||
if (stream_) {
|
||||
CHECK(!initialize_callback_id_.get());
|
||||
AllowJavascript();
|
||||
|
||||
auto stream_info = std::make_unique<base::DictionaryValue>();
|
||||
PopulateStreamInfo(stream_info.get(), stream_, original_url_);
|
||||
ResolveJavascriptCallback(*callback_id, *stream_info);
|
||||
} else {
|
||||
initialize_callback_id_ =
|
||||
base::Value::ToUniquePtrValue(callback_id.Clone());
|
||||
}
|
||||
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
zoom_controller->SetZoomMode(WebContentsZoomController::ZoomMode::MANUAL);
|
||||
zoom_controller->SetZoomLevel(0);
|
||||
}
|
||||
|
||||
void PdfViewerHandler::GetDefaultZoom(const base::ListValue* args) {
|
||||
if (!IsJavascriptAllowed())
|
||||
return;
|
||||
CHECK_EQ(1U, args->GetSize());
|
||||
const base::Value* callback_id;
|
||||
CHECK(args->Get(0, &callback_id));
|
||||
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
double zoom_level = zoom_controller->GetDefaultZoomLevel();
|
||||
ResolveJavascriptCallback(
|
||||
*callback_id, base::Value(content::ZoomLevelToZoomFactor(zoom_level)));
|
||||
}
|
||||
|
||||
void PdfViewerHandler::GetInitialZoom(const base::ListValue* args) {
|
||||
if (!IsJavascriptAllowed())
|
||||
return;
|
||||
CHECK_EQ(1U, args->GetSize());
|
||||
const base::Value* callback_id;
|
||||
CHECK(args->Get(0, &callback_id));
|
||||
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
double zoom_level = zoom_controller->GetZoomLevel();
|
||||
ResolveJavascriptCallback(
|
||||
*callback_id, base::Value(content::ZoomLevelToZoomFactor(zoom_level)));
|
||||
}
|
||||
|
||||
void PdfViewerHandler::SetZoom(const base::ListValue* args) {
|
||||
if (!IsJavascriptAllowed())
|
||||
return;
|
||||
CHECK_EQ(2U, args->GetSize());
|
||||
const base::Value* callback_id;
|
||||
CHECK(args->Get(0, &callback_id));
|
||||
double zoom_level = 0.0;
|
||||
CHECK(args->GetDouble(1, &zoom_level));
|
||||
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
zoom_controller->SetZoomLevel(zoom_level);
|
||||
ResolveJavascriptCallback(*callback_id, base::Value(zoom_level));
|
||||
}
|
||||
|
||||
void PdfViewerHandler::GetStrings(const base::ListValue* args) {
|
||||
if (!IsJavascriptAllowed())
|
||||
return;
|
||||
CHECK_EQ(1U, args->GetSize());
|
||||
const base::Value* callback_id;
|
||||
CHECK(args->Get(0, &callback_id));
|
||||
|
||||
auto result = std::make_unique<base::DictionaryValue>();
|
||||
// TODO(deepak1556): Generate strings from components/pdf_strings.grdp.
|
||||
#define SET_STRING(id, resource) result->SetString(id, resource)
|
||||
SET_STRING("passwordPrompt",
|
||||
"This document is password protected. Please enter a password.");
|
||||
SET_STRING("passwordSubmit", "Submit");
|
||||
SET_STRING("passwordInvalid", "Incorrect password");
|
||||
SET_STRING("pageLoading", "Loading...");
|
||||
SET_STRING("pageLoadFailed", "Failed to load PDF document");
|
||||
SET_STRING("pageReload", "Reload");
|
||||
SET_STRING("bookmarks", "Bookmarks");
|
||||
SET_STRING("labelPageNumber", "Page number");
|
||||
SET_STRING("tooltipRotateCW", "Rotate clockwise");
|
||||
SET_STRING("tooltipDownload", "Download");
|
||||
SET_STRING("tooltipFitToPage", "Fit to page");
|
||||
SET_STRING("tooltipFitToWidth", "Fit to width");
|
||||
SET_STRING("tooltipZoomIn", "Zoom in");
|
||||
SET_STRING("tooltipZoomOut", "Zoom out");
|
||||
#undef SET_STRING
|
||||
|
||||
webui::SetLoadTimeDataDefaults(g_browser_process->GetApplicationLocale(),
|
||||
result.get());
|
||||
ResolveJavascriptCallback(*callback_id, *result);
|
||||
}
|
||||
|
||||
void PdfViewerHandler::Reload(const base::ListValue* args) {
|
||||
CHECK_EQ(0U, args->GetSize());
|
||||
web_ui()->GetWebContents()->ReloadFocusedFrame(false);
|
||||
}
|
||||
|
||||
void PdfViewerHandler::OnZoomLevelChanged(content::WebContents* web_contents,
|
||||
double level,
|
||||
bool is_temporary) {
|
||||
if (web_ui()->GetWebContents() == web_contents) {
|
||||
CallJavascriptFunction("cr.webUIListenerCallback",
|
||||
base::Value("onZoomLevelChanged"),
|
||||
base::Value(content::ZoomLevelToZoomFactor(level)));
|
||||
}
|
||||
}
|
||||
|
||||
void PdfViewerHandler::AddObserver() {
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
zoom_controller->AddObserver(this);
|
||||
}
|
||||
|
||||
void PdfViewerHandler::RemoveObserver() {
|
||||
auto zoom_controller =
|
||||
WebContentsZoomController::FromWebContents(web_ui()->GetWebContents());
|
||||
zoom_controller->RemoveObserver(this);
|
||||
}
|
||||
|
||||
} // namespace atom
|
61
shell/browser/ui/webui/pdf_viewer_handler.h
Normal file
61
shell/browser/ui/webui/pdf_viewer_handler.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_
|
||||
#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/web_contents_zoom_controller.h"
|
||||
#include "base/macros.h"
|
||||
#include "content/public/browser/host_zoom_map.h"
|
||||
#include "content/public/browser/web_ui_message_handler.h"
|
||||
|
||||
namespace base {
|
||||
class ListValue;
|
||||
}
|
||||
|
||||
namespace content {
|
||||
struct StreamInfo;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class PdfViewerHandler : public content::WebUIMessageHandler,
|
||||
public WebContentsZoomController::Observer {
|
||||
public:
|
||||
explicit PdfViewerHandler(const std::string& original_url);
|
||||
~PdfViewerHandler() override;
|
||||
|
||||
void SetPdfResourceStream(content::StreamInfo* stream);
|
||||
|
||||
protected:
|
||||
// WebUIMessageHandler implementation.
|
||||
void RegisterMessages() override;
|
||||
void OnJavascriptAllowed() override;
|
||||
void OnJavascriptDisallowed() override;
|
||||
|
||||
private:
|
||||
void Initialize(const base::ListValue* args);
|
||||
void GetDefaultZoom(const base::ListValue* args);
|
||||
void GetInitialZoom(const base::ListValue* args);
|
||||
void SetZoom(const base::ListValue* args);
|
||||
void GetStrings(const base::ListValue* args);
|
||||
void Reload(const base::ListValue* args);
|
||||
void OnZoomLevelChanged(content::WebContents* web_contents,
|
||||
double level,
|
||||
bool is_temporary);
|
||||
void AddObserver();
|
||||
void RemoveObserver();
|
||||
std::unique_ptr<base::Value> initialize_callback_id_;
|
||||
content::StreamInfo* stream_ = nullptr;
|
||||
std::string original_url_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PdfViewerHandler);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_HANDLER_H_
|
258
shell/browser/ui/webui/pdf_viewer_ui.cc
Normal file
258
shell/browser/ui/webui/pdf_viewer_ui.cc
Normal file
|
@ -0,0 +1,258 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/webui/pdf_viewer_ui.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/browser/atom_browser_context.h"
|
||||
#include "atom/browser/loader/layered_resource_handler.h"
|
||||
#include "atom/browser/ui/webui/pdf_viewer_handler.h"
|
||||
#include "atom/common/atom_constants.h"
|
||||
#include "base/sequenced_task_runner_helpers.h"
|
||||
#include "base/task/post_task.h"
|
||||
#include "content/browser/loader/resource_dispatcher_host_impl.h"
|
||||
#include "content/browser/loader/resource_request_info_impl.h"
|
||||
#include "content/browser/loader/stream_resource_handler.h"
|
||||
#include "content/browser/resource_context_impl.h"
|
||||
#include "content/browser/streams/stream.h"
|
||||
#include "content/browser/streams/stream_context.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/resource_context.h"
|
||||
#include "content/public/browser/stream_handle.h"
|
||||
#include "content/public/browser/stream_info.h"
|
||||
#include "content/public/browser/url_data_source.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "grit/pdf_viewer_resources_map.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/base/mime_util.h"
|
||||
#include "net/url_request/url_request.h"
|
||||
#include "net/url_request/url_request_context.h"
|
||||
#include "services/network/public/cpp/resource_response.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
|
||||
using content::BrowserThread;
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// Extracts the path value from the URL without the leading '/',
|
||||
// which follows the mapping of names in pdf_viewer_resources_map.
|
||||
std::string PathWithoutParams(const std::string& path) {
|
||||
return GURL(kPdfViewerUIOrigin + path).path().substr(1);
|
||||
}
|
||||
|
||||
class BundledDataSource : public content::URLDataSource {
|
||||
public:
|
||||
BundledDataSource() {
|
||||
for (size_t i = 0; i < kPdfViewerResourcesSize; ++i) {
|
||||
std::string resource_path = kPdfViewerResources[i].name;
|
||||
DCHECK(path_to_resource_id_.find(resource_path) ==
|
||||
path_to_resource_id_.end());
|
||||
path_to_resource_id_[resource_path] = kPdfViewerResources[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
// content::URLDataSource implementation.
|
||||
std::string GetSource() const override { return kPdfViewerUIHost; }
|
||||
|
||||
void StartDataRequest(
|
||||
const std::string& path,
|
||||
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
|
||||
const GotDataCallback& callback) override {
|
||||
std::string filename = PathWithoutParams(path);
|
||||
auto entry = path_to_resource_id_.find(filename);
|
||||
|
||||
if (entry != path_to_resource_id_.end()) {
|
||||
int resource_id = entry->second;
|
||||
const ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
|
||||
callback.Run(rb.LoadDataResourceBytes(resource_id));
|
||||
} else {
|
||||
LOG(ERROR) << "Unable to find: " << path;
|
||||
callback.Run(new base::RefCountedString());
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetMimeType(const std::string& path) const override {
|
||||
base::FilePath::StringType ext =
|
||||
base::FilePath::FromUTF8Unsafe(PathWithoutParams(path)).Extension();
|
||||
std::string mime_type;
|
||||
if (!ext.empty() &&
|
||||
net::GetWellKnownMimeTypeFromExtension(ext.substr(1), &mime_type))
|
||||
return mime_type;
|
||||
return "text/html";
|
||||
}
|
||||
|
||||
bool ShouldAddContentSecurityPolicy() const override { return false; }
|
||||
|
||||
bool ShouldDenyXFrameOptions() const override { return false; }
|
||||
|
||||
bool ShouldServeMimeTypeAsContentTypeHeader() const override { return true; }
|
||||
|
||||
private:
|
||||
~BundledDataSource() override {}
|
||||
|
||||
// A map from a resource path to the resource ID.
|
||||
std::map<std::string, int> path_to_resource_id_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BundledDataSource);
|
||||
};
|
||||
|
||||
// Helper to convert from OnceCallback to Callback.
|
||||
template <typename T>
|
||||
void CallMigrationCallback(T callback,
|
||||
std::unique_ptr<content::StreamInfo> stream_info) {
|
||||
std::move(callback).Run(std::move(stream_info));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class PdfViewerUI::ResourceRequester
|
||||
: public base::RefCountedThreadSafe<ResourceRequester,
|
||||
BrowserThread::DeleteOnIOThread>,
|
||||
public atom::LayeredResourceHandler::Delegate {
|
||||
public:
|
||||
explicit ResourceRequester(StreamResponseCallback cb)
|
||||
: stream_response_cb_(std::move(cb)) {}
|
||||
|
||||
void StartRequest(const GURL& url,
|
||||
const GURL& origin,
|
||||
int render_process_id,
|
||||
int render_view_id,
|
||||
int render_frame_id,
|
||||
content::ResourceContext* resource_context) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
||||
|
||||
const net::URLRequestContext* request_context =
|
||||
resource_context->GetRequestContext();
|
||||
std::unique_ptr<net::URLRequest> request(
|
||||
request_context->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr));
|
||||
request->set_method("GET");
|
||||
|
||||
content::ResourceDispatcherHostImpl::Get()->InitializeURLRequest(
|
||||
request.get(), content::Referrer(url, blink::kWebReferrerPolicyDefault),
|
||||
false, // download.
|
||||
render_process_id, render_view_id, render_frame_id,
|
||||
content::PREVIEWS_OFF, resource_context);
|
||||
|
||||
content::ResourceRequestInfoImpl* info =
|
||||
content::ResourceRequestInfoImpl::ForRequest(request.get());
|
||||
content::StreamContext* stream_context =
|
||||
content::GetStreamContextForResourceContext(resource_context);
|
||||
|
||||
std::unique_ptr<content::ResourceHandler> handler =
|
||||
std::make_unique<content::StreamResourceHandler>(
|
||||
request.get(), stream_context->registry(), origin, false);
|
||||
info->set_is_stream(true);
|
||||
stream_info_.reset(new content::StreamInfo);
|
||||
stream_info_->handle =
|
||||
static_cast<content::StreamResourceHandler*>(handler.get())
|
||||
->stream()
|
||||
->CreateHandle();
|
||||
stream_info_->original_url = request->url();
|
||||
|
||||
// Helper to fill stream response details.
|
||||
handler.reset(new atom::LayeredResourceHandler(request.get(),
|
||||
std::move(handler), this));
|
||||
|
||||
content::ResourceDispatcherHostImpl::Get()->BeginURLRequest(
|
||||
std::move(request), std::move(handler),
|
||||
false, // download
|
||||
false, // content_initiated (download specific)
|
||||
false, // do_not_prompt_for_login (download specific)
|
||||
resource_context);
|
||||
}
|
||||
|
||||
protected:
|
||||
// atom::LayeredResourceHandler::Delegate:
|
||||
void OnResponseStarted(network::ResourceResponse* response) override {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
||||
|
||||
auto resource_response_head = response->head;
|
||||
auto headers = resource_response_head.headers;
|
||||
auto mime_type = resource_response_head.mime_type;
|
||||
if (headers.get())
|
||||
stream_info_->response_headers =
|
||||
new net::HttpResponseHeaders(headers->raw_headers());
|
||||
stream_info_->mime_type = mime_type;
|
||||
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {BrowserThread::UI},
|
||||
base::BindOnce(&CallMigrationCallback<StreamResponseCallback>,
|
||||
base::Passed(&stream_response_cb_),
|
||||
base::Passed(&stream_info_)));
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
|
||||
friend class base::DeleteHelper<ResourceRequester>;
|
||||
~ResourceRequester() override {}
|
||||
|
||||
StreamResponseCallback stream_response_cb_;
|
||||
std::unique_ptr<content::StreamInfo> stream_info_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceRequester);
|
||||
};
|
||||
|
||||
PdfViewerUI::PdfViewerUI(content::BrowserContext* browser_context,
|
||||
content::WebUI* web_ui,
|
||||
const std::string& src)
|
||||
: content::WebUIController(web_ui),
|
||||
content::WebContentsObserver(web_ui->GetWebContents()),
|
||||
src_(src) {
|
||||
pdf_handler_ = new PdfViewerHandler(src);
|
||||
web_ui->AddMessageHandler(
|
||||
std::unique_ptr<content::WebUIMessageHandler>(pdf_handler_));
|
||||
content::URLDataSource::Add(browser_context, new BundledDataSource);
|
||||
}
|
||||
|
||||
PdfViewerUI::~PdfViewerUI() {}
|
||||
|
||||
bool PdfViewerUI::OnMessageReceived(
|
||||
const IPC::Message& message,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
bool handled = true;
|
||||
IPC_BEGIN_MESSAGE_MAP(PdfViewerUI, message)
|
||||
IPC_MESSAGE_UNHANDLED(handled = false)
|
||||
IPC_END_MESSAGE_MAP()
|
||||
return handled;
|
||||
}
|
||||
|
||||
void PdfViewerUI::OnPdfStreamCreated(
|
||||
std::unique_ptr<content::StreamInfo> stream) {
|
||||
stream_ = std::move(stream);
|
||||
if (pdf_handler_)
|
||||
pdf_handler_->SetPdfResourceStream(stream_.get());
|
||||
resource_requester_ = nullptr;
|
||||
}
|
||||
|
||||
void PdfViewerUI::RenderFrameCreated(content::RenderFrameHost* rfh) {
|
||||
int render_process_id = rfh->GetProcess()->GetID();
|
||||
int render_frame_id = rfh->GetRoutingID();
|
||||
int render_view_id = rfh->GetRenderViewHost()->GetRoutingID();
|
||||
auto resource_context =
|
||||
web_contents()->GetBrowserContext()->GetResourceContext();
|
||||
auto callback =
|
||||
base::BindOnce(&PdfViewerUI::OnPdfStreamCreated, base::Unretained(this));
|
||||
resource_requester_ = new ResourceRequester(std::move(callback));
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {BrowserThread::IO},
|
||||
base::BindOnce(&ResourceRequester::StartRequest, resource_requester_,
|
||||
GURL(src_), GURL(kPdfViewerUIOrigin), render_process_id,
|
||||
render_view_id, render_frame_id, resource_context));
|
||||
}
|
||||
|
||||
void PdfViewerUI::OnSaveURLAs(const GURL& url,
|
||||
const content::Referrer& referrer) {
|
||||
web_contents()->SaveFrame(url, referrer);
|
||||
}
|
||||
|
||||
} // namespace atom
|
61
shell/browser/ui/webui/pdf_viewer_ui.h
Normal file
61
shell/browser/ui/webui/pdf_viewer_ui.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_
|
||||
#define ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "content/public/browser/web_ui_controller.h"
|
||||
#include "ipc/ipc_message.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
struct StreamInfo;
|
||||
} // namespace content
|
||||
|
||||
namespace atom {
|
||||
|
||||
class PdfViewerHandler;
|
||||
|
||||
class PdfViewerUI : public content::WebUIController,
|
||||
public content::WebContentsObserver {
|
||||
public:
|
||||
PdfViewerUI(content::BrowserContext* browser_context,
|
||||
content::WebUI* web_ui,
|
||||
const std::string& src);
|
||||
~PdfViewerUI() override;
|
||||
|
||||
// content::WebContentsObserver:
|
||||
bool OnMessageReceived(const IPC::Message& message,
|
||||
content::RenderFrameHost* render_frame_host) override;
|
||||
void RenderFrameCreated(content::RenderFrameHost* rfh) override;
|
||||
|
||||
private:
|
||||
using StreamResponseCallback =
|
||||
base::OnceCallback<void(std::unique_ptr<content::StreamInfo>)>;
|
||||
class ResourceRequester;
|
||||
|
||||
void OnPdfStreamCreated(std::unique_ptr<content::StreamInfo> stream_info);
|
||||
void OnSaveURLAs(const GURL& url, const content::Referrer& referrer);
|
||||
|
||||
// Source URL from where the PDF originates.
|
||||
std::string src_;
|
||||
|
||||
PdfViewerHandler* pdf_handler_;
|
||||
|
||||
scoped_refptr<ResourceRequester> resource_requester_;
|
||||
|
||||
// Pdf Resource stream.
|
||||
std::unique_ptr<content::StreamInfo> stream_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PdfViewerUI);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_BROWSER_UI_WEBUI_PDF_VIEWER_UI_H_
|
59
shell/browser/ui/win/atom_desktop_native_widget_aura.cc
Normal file
59
shell/browser/ui/win/atom_desktop_native_widget_aura.cc
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/win/atom_desktop_native_widget_aura.h"
|
||||
|
||||
#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h"
|
||||
#include "ui/views/corewm/tooltip_controller.h"
|
||||
#include "ui/wm/public/tooltip_client.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
AtomDesktopNativeWidgetAura::AtomDesktopNativeWidgetAura(
|
||||
NativeWindowViews* native_window_view)
|
||||
: views::DesktopNativeWidgetAura(native_window_view->widget()),
|
||||
native_window_view_(native_window_view) {
|
||||
GetNativeWindow()->SetName("AtomDesktopNativeWidgetAura");
|
||||
// This is to enable the override of OnWindowActivated
|
||||
wm::SetActivationChangeObserver(GetNativeWindow(), this);
|
||||
}
|
||||
|
||||
void AtomDesktopNativeWidgetAura::InitNativeWidget(
|
||||
const views::Widget::InitParams& params) {
|
||||
views::Widget::InitParams modified_params = params;
|
||||
desktop_window_tree_host_ = new AtomDesktopWindowTreeHostWin(
|
||||
native_window_view_,
|
||||
static_cast<views::DesktopNativeWidgetAura*>(params.native_widget));
|
||||
modified_params.desktop_window_tree_host = desktop_window_tree_host_;
|
||||
views::DesktopNativeWidgetAura::InitNativeWidget(modified_params);
|
||||
}
|
||||
|
||||
void AtomDesktopNativeWidgetAura::Activate() {
|
||||
// Activate can cause the focused window to be blurred so only
|
||||
// call when the window being activated is visible. This prevents
|
||||
// hidden windows from blurring the focused window when created.
|
||||
if (IsVisible())
|
||||
views::DesktopNativeWidgetAura::Activate();
|
||||
}
|
||||
|
||||
void AtomDesktopNativeWidgetAura::OnWindowActivated(
|
||||
wm::ActivationChangeObserver::ActivationReason reason,
|
||||
aura::Window* gained_active,
|
||||
aura::Window* lost_active) {
|
||||
views::DesktopNativeWidgetAura::OnWindowActivated(reason, gained_active,
|
||||
lost_active);
|
||||
if (lost_active != nullptr) {
|
||||
auto* tooltip_controller = static_cast<views::corewm::TooltipController*>(
|
||||
wm::GetTooltipClient(lost_active->GetRootWindow()));
|
||||
|
||||
// This will cause the tooltip to be hidden when a window is deactivated,
|
||||
// as it should be.
|
||||
// TODO(brenca): Remove this fix when the chromium issue is fixed.
|
||||
// crbug.com/724538
|
||||
if (tooltip_controller != nullptr)
|
||||
tooltip_controller->OnCancelMode(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace atom
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue