Move all sources under atom/.
This commit is contained in:
parent
26ddbbb0ee
commit
516d46444d
217 changed files with 519 additions and 519 deletions
218
atom/browser/ui/accelerator_util.cc
Normal file
218
atom/browser/ui/accelerator_util.cc
Normal file
|
@ -0,0 +1,218 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/accelerator_util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "ui/base/models/simple_menu_model.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
namespace {
|
||||
|
||||
// Return key code of the char.
|
||||
ui::KeyboardCode KeyboardCodeFromCharCode(char c, bool* shifted) {
|
||||
*shifted = false;
|
||||
switch (c) {
|
||||
case 8: case 0x7F: return ui::VKEY_BACK;
|
||||
case 9: return ui::VKEY_TAB;
|
||||
case 0xD: case 3: return ui::VKEY_RETURN;
|
||||
case 0x1B: return ui::VKEY_ESCAPE;
|
||||
case ' ': return ui::VKEY_SPACE;
|
||||
|
||||
case 'a': return ui::VKEY_A;
|
||||
case 'b': return ui::VKEY_B;
|
||||
case 'c': return ui::VKEY_C;
|
||||
case 'd': return ui::VKEY_D;
|
||||
case 'e': return ui::VKEY_E;
|
||||
case 'f': return ui::VKEY_F;
|
||||
case 'g': return ui::VKEY_G;
|
||||
case 'h': return ui::VKEY_H;
|
||||
case 'i': return ui::VKEY_I;
|
||||
case 'j': return ui::VKEY_J;
|
||||
case 'k': return ui::VKEY_K;
|
||||
case 'l': return ui::VKEY_L;
|
||||
case 'm': return ui::VKEY_M;
|
||||
case 'n': return ui::VKEY_N;
|
||||
case 'o': return ui::VKEY_O;
|
||||
case 'p': return ui::VKEY_P;
|
||||
case 'q': return ui::VKEY_Q;
|
||||
case 'r': return ui::VKEY_R;
|
||||
case 's': return ui::VKEY_S;
|
||||
case 't': return ui::VKEY_T;
|
||||
case 'u': return ui::VKEY_U;
|
||||
case 'v': return ui::VKEY_V;
|
||||
case 'w': return ui::VKEY_W;
|
||||
case 'x': return ui::VKEY_X;
|
||||
case 'y': return ui::VKEY_Y;
|
||||
case 'z': return ui::VKEY_Z;
|
||||
|
||||
case ')': *shifted = true; case '0': return ui::VKEY_0;
|
||||
case '!': *shifted = true; case '1': return ui::VKEY_1;
|
||||
case '@': *shifted = true; case '2': return ui::VKEY_2;
|
||||
case '#': *shifted = true; case '3': return ui::VKEY_3;
|
||||
case '$': *shifted = true; case '4': return ui::VKEY_4;
|
||||
case '%': *shifted = true; case '5': return ui::VKEY_5;
|
||||
case '^': *shifted = true; case '6': return ui::VKEY_6;
|
||||
case '&': *shifted = true; case '7': return ui::VKEY_7;
|
||||
case '*': *shifted = true; case '8': return ui::VKEY_8;
|
||||
case '(': *shifted = true; case '9': return ui::VKEY_9;
|
||||
|
||||
case ':': *shifted = true; case ';': return ui::VKEY_OEM_1;
|
||||
case '+': *shifted = true; case '=': return ui::VKEY_OEM_PLUS;
|
||||
case '<': *shifted = true; case ',': return ui::VKEY_OEM_COMMA;
|
||||
case '_': *shifted = true; case '-': return ui::VKEY_OEM_MINUS;
|
||||
case '>': *shifted = true; case '.': return ui::VKEY_OEM_PERIOD;
|
||||
case '?': *shifted = true; case '/': return ui::VKEY_OEM_2;
|
||||
case '~': *shifted = true; case '`': return ui::VKEY_OEM_3;
|
||||
case '{': *shifted = true; case '[': return ui::VKEY_OEM_4;
|
||||
case '|': *shifted = true; case '\\': return ui::VKEY_OEM_5;
|
||||
case '}': *shifted = true; case ']': return ui::VKEY_OEM_6;
|
||||
case '"': *shifted = true; case '\'': return ui::VKEY_OEM_7;
|
||||
|
||||
default: return ui::VKEY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool StringToAccelerator(const std::string& description,
|
||||
ui::Accelerator* accelerator) {
|
||||
if (!IsStringASCII(description)) {
|
||||
LOG(ERROR) << "The accelerator string can only contain ASCII characters";
|
||||
return false;
|
||||
}
|
||||
std::string shortcut(StringToLowerASCII(description));
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
base::SplitString(shortcut, '+', &tokens);
|
||||
|
||||
// Now, parse it into an accelerator.
|
||||
int modifiers = ui::EF_NONE;
|
||||
ui::KeyboardCode key = ui::VKEY_UNKNOWN;
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
// We use straight comparing instead of map because the accelerator tends
|
||||
// to be correct and usually only uses few special tokens.
|
||||
if (tokens[i].size() == 1) {
|
||||
bool shifted = false;
|
||||
key = KeyboardCodeFromCharCode(tokens[i][0], &shifted);
|
||||
if (shifted)
|
||||
modifiers |= ui::EF_SHIFT_DOWN;
|
||||
} else if (tokens[i] == "ctrl" || tokens[i] == "control") {
|
||||
modifiers |= ui::EF_CONTROL_DOWN;
|
||||
} else if (tokens[i] == "cmd" || tokens[i] == "command") {
|
||||
modifiers |= ui::EF_COMMAND_DOWN;
|
||||
} else if (tokens[i] == "commandorcontrol" || tokens[i] == "cmdorctrl") {
|
||||
#if defined(OS_MACOSX)
|
||||
modifiers |= ui::EF_COMMAND_DOWN;
|
||||
#else
|
||||
modifiers |= ui::EF_CONTROL_DOWN;
|
||||
#endif
|
||||
} else if (tokens[i] == "alt") {
|
||||
modifiers |= ui::EF_ALT_DOWN;
|
||||
} else if (tokens[i] == "shift") {
|
||||
modifiers |= ui::EF_SHIFT_DOWN;
|
||||
} else if (tokens[i] == "tab") {
|
||||
key = ui::VKEY_TAB;
|
||||
} else if (tokens[i] == "space") {
|
||||
key = ui::VKEY_SPACE;
|
||||
} else if (tokens[i] == "backspace") {
|
||||
key = ui::VKEY_BACK;
|
||||
} else if (tokens[i] == "delete") {
|
||||
key = ui::VKEY_DELETE;
|
||||
} else if (tokens[i] == "enter" || tokens[i] == "return") {
|
||||
key = ui::VKEY_RETURN;
|
||||
} else if (tokens[i] == "up") {
|
||||
key = ui::VKEY_UP;
|
||||
} else if (tokens[i] == "down") {
|
||||
key = ui::VKEY_DOWN;
|
||||
} else if (tokens[i] == "left") {
|
||||
key = ui::VKEY_LEFT;
|
||||
} else if (tokens[i] == "right") {
|
||||
key = ui::VKEY_RIGHT;
|
||||
} else if (tokens[i] == "home") {
|
||||
key = ui::VKEY_HOME;
|
||||
} else if (tokens[i] == "end") {
|
||||
key = ui::VKEY_END;
|
||||
} else if (tokens[i] == "pagedown") {
|
||||
key = ui::VKEY_PRIOR;
|
||||
} else if (tokens[i] == "pageup") {
|
||||
key = ui::VKEY_NEXT;
|
||||
} else if (tokens[i] == "esc") {
|
||||
key = ui::VKEY_ESCAPE;
|
||||
} else if (tokens[i] == "volumemute") {
|
||||
key = ui::VKEY_VOLUME_MUTE;
|
||||
} else if (tokens[i] == "volumeup") {
|
||||
key = ui::VKEY_VOLUME_UP;
|
||||
} else if (tokens[i] == "volumedown") {
|
||||
key = ui::VKEY_VOLUME_DOWN;
|
||||
} else if (tokens[i] == "medianexttrack") {
|
||||
key = ui::VKEY_MEDIA_NEXT_TRACK;
|
||||
} else if (tokens[i] == "mediaprevioustrack") {
|
||||
key = ui::VKEY_MEDIA_PREV_TRACK;
|
||||
} else if (tokens[i] == "mediastop") {
|
||||
key = ui::VKEY_MEDIA_STOP;
|
||||
} else if (tokens[i] == "mediaplaypause") {
|
||||
key = ui::VKEY_MEDIA_PLAY_PAUSE;
|
||||
} else if (tokens[i].size() > 1 && tokens[i][0] == 'f') {
|
||||
// F1 - F24.
|
||||
int n;
|
||||
if (base::StringToInt(tokens[i].c_str() + 1, &n)) {
|
||||
key = static_cast<ui::KeyboardCode>(ui::VKEY_F1 + n - 1);
|
||||
} else {
|
||||
LOG(WARNING) << tokens[i] << "is not available on keyboard";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "Invalid accelerator token: " << tokens[i];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == ui::VKEY_UNKNOWN) {
|
||||
LOG(WARNING) << "The accelerator doesn't contain a valid key";
|
||||
return false;
|
||||
}
|
||||
|
||||
*accelerator = ui::Accelerator(key, modifiers);
|
||||
SetPlatformAccelerator(accelerator);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenerateAcceleratorTable(AcceleratorTable* table, ui::MenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ui::MenuModel::ItemType type = model->GetTypeAt(i);
|
||||
if (type == ui::MenuModel::TYPE_SUBMENU) {
|
||||
ui::MenuModel* submodel = model->GetSubmenuModelAt(i);
|
||||
GenerateAcceleratorTable(table, submodel);
|
||||
} else {
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAt(i, &accelerator)) {
|
||||
MenuItem item = { i, model };
|
||||
(*table)[accelerator] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
const ui::Accelerator& accelerator) {
|
||||
if (ContainsKey(*table, accelerator)) {
|
||||
const accelerator_util::MenuItem& item = (*table)[accelerator];
|
||||
item.model->ActivatedAt(item.position);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace accelerator_util
|
38
atom/browser/ui/accelerator_util.h
Normal file
38
atom/browser/ui/accelerator_util.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BROWSER_UI_ACCELERATOR_UTIL_H_
|
||||
#define BROWSER_UI_ACCELERATOR_UTIL_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
typedef struct { int position; ui::MenuModel* model; } MenuItem;
|
||||
typedef std::map<ui::Accelerator, MenuItem> AcceleratorTable;
|
||||
|
||||
// Parse a string as an accelerator.
|
||||
bool StringToAccelerator(const std::string& description,
|
||||
ui::Accelerator* accelerator);
|
||||
|
||||
// Set platform accelerator for the Accelerator.
|
||||
void SetPlatformAccelerator(ui::Accelerator* accelerator);
|
||||
|
||||
// Generate a table that contains memu model's accelerators and command ids.
|
||||
void GenerateAcceleratorTable(AcceleratorTable* table, ui::MenuModel* model);
|
||||
|
||||
// Trigger command from the accelerators table.
|
||||
bool TriggerAcceleratorTableCommand(AcceleratorTable* table,
|
||||
const ui::Accelerator& accelerator);
|
||||
|
||||
} // namespace accelerator_util
|
||||
|
||||
#endif // BROWSER_UI_ACCELERATOR_UTIL_H_
|
20
atom/browser/ui/accelerator_util_gtk.cc
Normal file
20
atom/browser/ui/accelerator_util_gtk.cc
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/accelerator_util.h"
|
||||
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/platform_accelerator_gtk.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
void SetPlatformAccelerator(ui::Accelerator* accelerator) {
|
||||
scoped_ptr<ui::PlatformAccelerator> platform_accelerator(
|
||||
new ui::PlatformAcceleratorGtk(
|
||||
GetGdkKeyCodeForAccelerator(*accelerator),
|
||||
GetGdkModifierForAccelerator(*accelerator)));
|
||||
accelerator->set_platform_accelerator(platform_accelerator.Pass());
|
||||
}
|
||||
|
||||
} // namespace accelerator_util
|
34
atom/browser/ui/accelerator_util_mac.mm
Normal file
34
atom/browser/ui/accelerator_util_mac.mm
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/accelerator_util.h"
|
||||
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#import "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||
#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
void SetPlatformAccelerator(ui::Accelerator* accelerator) {
|
||||
unichar character;
|
||||
unichar characterIgnoringModifiers;
|
||||
ui::MacKeyCodeForWindowsKeyCode(accelerator->key_code(),
|
||||
0,
|
||||
&character,
|
||||
&characterIgnoringModifiers);
|
||||
NSString* characters =
|
||||
[[[NSString alloc] initWithCharacters:&character length:1] autorelease];
|
||||
|
||||
NSUInteger modifiers =
|
||||
(accelerator->IsCtrlDown() ? NSControlKeyMask : 0) |
|
||||
(accelerator->IsCmdDown() ? NSCommandKeyMask : 0) |
|
||||
(accelerator->IsAltDown() ? NSAlternateKeyMask : 0) |
|
||||
(accelerator->IsShiftDown() ? NSShiftKeyMask : 0);
|
||||
|
||||
scoped_ptr<ui::PlatformAccelerator> platform_accelerator(
|
||||
new ui::PlatformAcceleratorCocoa(characters, modifiers));
|
||||
accelerator->set_platform_accelerator(platform_accelerator.Pass());
|
||||
}
|
||||
|
||||
} // namespace accelerator_util
|
14
atom/browser/ui/accelerator_util_win.cc
Normal file
14
atom/browser/ui/accelerator_util_win.cc
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/accelerator_util.h"
|
||||
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
|
||||
namespace accelerator_util {
|
||||
|
||||
void SetPlatformAccelerator(ui::Accelerator* accelerator) {
|
||||
}
|
||||
|
||||
} // namespace accelerator_util
|
72
atom/browser/ui/cocoa/atom_menu_controller.h
Normal file
72
atom/browser/ui/cocoa/atom_menu_controller.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// 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_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/mac/scoped_nsobject.h"
|
||||
#include "base/strings/string16.h"
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
// 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
|
||||
ui::MenuModel* model_; // weak
|
||||
base::scoped_nsobject<NSMenu> menu_;
|
||||
BOOL isMenuOpen_;
|
||||
}
|
||||
|
||||
@property(nonatomic, assign) ui::MenuModel* model;
|
||||
|
||||
// NIB-based initializer. This does not create a menu. Clients can set the
|
||||
// properties of the object and the menu will be created upon the first call to
|
||||
// |-menu|. Note that the menu will be immutable after creation.
|
||||
- (id)init;
|
||||
|
||||
// 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:(ui::MenuModel*)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
|
||||
|
||||
// Exposed only for unit testing, do not call directly.
|
||||
@interface AtomMenuController (PrivateExposedForTesting)
|
||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item;
|
||||
@end
|
||||
|
||||
// Protected methods that subclassers can override.
|
||||
@interface AtomMenuController (Protected)
|
||||
- (void)addItemToMenu:(NSMenu*)menu
|
||||
atIndex:(NSInteger)index
|
||||
fromModel:(ui::MenuModel*)model;
|
||||
- (NSMenu*)menuFromModel:(ui::MenuModel*)model;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_
|
258
atom/browser/ui/cocoa/atom_menu_controller.mm
Normal file
258
atom/browser/ui/cocoa/atom_menu_controller.mm
Normal file
|
@ -0,0 +1,258 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// 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.
|
||||
|
||||
#import "atom/browser/ui/cocoa/atom_menu_controller.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/sys_string_conversions.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/base/models/simple_menu_model.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool isLeftButtonEvent(NSEvent* event) {
|
||||
NSEventType type = [event type];
|
||||
return type == NSLeftMouseDown ||
|
||||
type == NSLeftMouseDragged ||
|
||||
type == NSLeftMouseUp;
|
||||
}
|
||||
|
||||
bool isRightButtonEvent(NSEvent* event) {
|
||||
NSEventType type = [event type];
|
||||
return type == NSRightMouseDown ||
|
||||
type == NSRightMouseDragged ||
|
||||
type == NSRightMouseUp;
|
||||
}
|
||||
|
||||
bool isMiddleButtonEvent(NSEvent* event) {
|
||||
if ([event buttonNumber] != 2)
|
||||
return false;
|
||||
|
||||
NSEventType type = [event type];
|
||||
return type == NSOtherMouseDown ||
|
||||
type == NSOtherMouseDragged ||
|
||||
type == NSOtherMouseUp;
|
||||
}
|
||||
|
||||
int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) {
|
||||
int flags = 0;
|
||||
flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0;
|
||||
flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0;
|
||||
flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0;
|
||||
flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0;
|
||||
flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0;
|
||||
flags |= isLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0;
|
||||
flags |= isRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0;
|
||||
flags |= isMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Retrieves a bitsum of ui::EventFlags from NSEvent.
|
||||
int EventFlagsFromNSEvent(NSEvent* event) {
|
||||
NSUInteger modifiers = [event modifierFlags];
|
||||
return EventFlagsFromNSEventWithModifiers(event, modifiers);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@interface AtomMenuController (Private)
|
||||
- (void)addSeparatorToMenu:(NSMenu*)menu
|
||||
atIndex:(int)index;
|
||||
@end
|
||||
|
||||
@implementation AtomMenuController
|
||||
|
||||
@synthesize model = model_;
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithModel:(ui::MenuModel*)model {
|
||||
if ((self = [super init])) {
|
||||
model_ = model;
|
||||
[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_ = NULL;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
if (isMenuOpen_) {
|
||||
[menu_ cancelTracking];
|
||||
model_->MenuClosed();
|
||||
isMenuOpen_ = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a NSMenu from the given model. If the model has submenus, this can
|
||||
// be invoked recursively.
|
||||
- (NSMenu*)menuFromModel:(ui::MenuModel*)model {
|
||||
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
|
||||
const int count = model->GetItemCount();
|
||||
for (int index = 0; index < count; index++) {
|
||||
if (model->GetTypeAt(index) == ui::MenuModel::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];
|
||||
}
|
||||
|
||||
// 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:(ui::MenuModel*)model {
|
||||
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()];
|
||||
|
||||
ui::MenuModel::ItemType type = model->GetTypeAt(index);
|
||||
if (type == ui::MenuModel::TYPE_SUBMENU) {
|
||||
// Recursively build a submenu from the sub-model at this index.
|
||||
[item setTarget:nil];
|
||||
[item setAction:nil];
|
||||
ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index);
|
||||
NSMenu* submenu =
|
||||
[self menuFromModel:(ui::SimpleMenuModel*)submenuModel];
|
||||
[submenu setTitle:[item title]];
|
||||
[item setSubmenu:submenu];
|
||||
|
||||
// Hack to set window and help menu.
|
||||
if ([[item title] isEqualToString:@"Window"] && [submenu numberOfItems] > 0)
|
||||
[NSApp setWindowsMenu:submenu];
|
||||
else if ([[item title] isEqualToString:@"Help"])
|
||||
[NSApp setHelpMenu:submenu];
|
||||
} 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];
|
||||
[item setTarget:self];
|
||||
NSValue* modelObject = [NSValue valueWithPointer:model];
|
||||
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAt(index, &accelerator)) {
|
||||
const ui::PlatformAcceleratorCocoa* platformAccelerator =
|
||||
static_cast<const ui::PlatformAcceleratorCocoa*>(
|
||||
accelerator.platform_accelerator());
|
||||
if (platformAccelerator) {
|
||||
[item setKeyEquivalent:platformAccelerator->characters()];
|
||||
[item setKeyEquivalentModifierMask:
|
||||
platformAccelerator->modifier_mask()];
|
||||
}
|
||||
}
|
||||
}
|
||||
[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. Also will update the title if
|
||||
// the item is marked as "dynamic".
|
||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
||||
SEL action = [item action];
|
||||
if (action != @selector(itemSelected:))
|
||||
return NO;
|
||||
|
||||
NSInteger modelIndex = [item tag];
|
||||
ui::MenuModel* model =
|
||||
static_cast<ui::MenuModel*>(
|
||||
[[(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))];
|
||||
if (model->IsItemDynamicAt(modelIndex)) {
|
||||
// Update the label and the icon.
|
||||
NSString* label =
|
||||
l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
|
||||
[(id)item setTitle:label];
|
||||
|
||||
gfx::Image icon;
|
||||
model->GetIconAt(modelIndex, &icon);
|
||||
[(id)item setImage:icon.IsEmpty() ? nil : icon.ToNSImage()];
|
||||
}
|
||||
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];
|
||||
ui::MenuModel* model =
|
||||
static_cast<ui::MenuModel*>(
|
||||
[[sender representedObject] pointerValue]);
|
||||
DCHECK(model);
|
||||
if (model) {
|
||||
int event_flags = EventFlagsFromNSEvent([NSApp currentEvent]);
|
||||
model->ActivatedAt(modelIndex, event_flags);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenu*)menu {
|
||||
if (!menu_ && model_) {
|
||||
menu_.reset([[self menuFromModel:model_] retain]);
|
||||
[menu_ setDelegate:self];
|
||||
}
|
||||
return menu_.get();
|
||||
}
|
||||
|
||||
- (BOOL)isMenuOpen {
|
||||
return isMenuOpen_;
|
||||
}
|
||||
|
||||
- (void)menuWillOpen:(NSMenu*)menu {
|
||||
isMenuOpen_ = YES;
|
||||
model_->MenuWillShow();
|
||||
}
|
||||
|
||||
- (void)menuDidClose:(NSMenu*)menu {
|
||||
if (isMenuOpen_) {
|
||||
model_->MenuClosed();
|
||||
isMenuOpen_ = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
30
atom/browser/ui/cocoa/event_processing_window.h
Normal file
30
atom/browser/ui/cocoa/event_processing_window.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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_EVENT_PROCESSING_WINDOW_H_
|
||||
#define ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
// Override NSWindow to access unhandled keyboard events (for command
|
||||
// processing); subclassing NSWindow is the only method to do
|
||||
// this.
|
||||
@interface EventProcessingWindow : NSWindow {
|
||||
@private
|
||||
BOOL redispatchingEvent_;
|
||||
BOOL eventHandled_;
|
||||
}
|
||||
|
||||
// Sends a key event to |NSApp sendEvent:|, but also makes sure that it's not
|
||||
// short-circuited to the RWHV. This is used to send keyboard events to the menu
|
||||
// and the cmd-` handler if a keyboard event comes back unhandled from the
|
||||
// renderer. The event must be of type |NSKeyDown|, |NSKeyUp|, or
|
||||
// |NSFlagsChanged|.
|
||||
// Returns |YES| if |event| has been handled.
|
||||
- (BOOL)redispatchKeyEvent:(NSEvent*)event;
|
||||
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)theEvent;
|
||||
@end
|
||||
|
||||
#endif // ATOM_BROWSER_UI_COCOA_EVENT_PROCESSING_WINDOW_H_
|
106
atom/browser/ui/cocoa/event_processing_window.mm
Normal file
106
atom/browser/ui/cocoa/event_processing_window.mm
Normal file
|
@ -0,0 +1,106 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "atom/browser/ui/cocoa/event_processing_window.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#import "content/public/browser/render_widget_host_view_mac_base.h"
|
||||
|
||||
@interface EventProcessingWindow ()
|
||||
// Duplicate the given key event, but changing the associated window.
|
||||
- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event;
|
||||
@end
|
||||
|
||||
@implementation EventProcessingWindow
|
||||
|
||||
- (BOOL)redispatchKeyEvent:(NSEvent*)event {
|
||||
DCHECK(event);
|
||||
NSEventType eventType = [event type];
|
||||
if (eventType != NSKeyDown &&
|
||||
eventType != NSKeyUp &&
|
||||
eventType != NSFlagsChanged) {
|
||||
NOTREACHED();
|
||||
return YES; // Pretend it's been handled in an effort to limit damage.
|
||||
}
|
||||
|
||||
// Ordinarily, the event's window should be this window. However, when
|
||||
// switching between normal and fullscreen mode, we switch out the window, and
|
||||
// the event's window might be the previous window (or even an earlier one if
|
||||
// the renderer is running slowly and several mode switches occur). In this
|
||||
// rare case, we synthesize a new key event so that its associate window
|
||||
// (number) is our own.
|
||||
if ([event window] != self)
|
||||
event = [self keyEventForWindow:self fromKeyEvent:event];
|
||||
|
||||
// Redispatch the event.
|
||||
eventHandled_ = YES;
|
||||
redispatchingEvent_ = YES;
|
||||
[NSApp sendEvent:event];
|
||||
redispatchingEvent_ = NO;
|
||||
|
||||
// If the event was not handled by [NSApp sendEvent:], the sendEvent:
|
||||
// method below will be called, and because |redispatchingEvent_| is YES,
|
||||
// |eventHandled_| will be set to NO.
|
||||
return eventHandled_;
|
||||
}
|
||||
|
||||
- (void)sendEvent:(NSEvent*)event {
|
||||
if (!redispatchingEvent_)
|
||||
[super sendEvent:event];
|
||||
else
|
||||
eventHandled_ = NO;
|
||||
}
|
||||
|
||||
- (NSEvent*)keyEventForWindow:(NSWindow*)window fromKeyEvent:(NSEvent*)event {
|
||||
NSEventType eventType = [event type];
|
||||
|
||||
// Convert the event's location from the original window's coordinates into
|
||||
// our own.
|
||||
NSPoint eventLoc = [event locationInWindow];
|
||||
eventLoc = [[event window] convertBaseToScreen:eventLoc];
|
||||
eventLoc = [self convertScreenToBase:eventLoc];
|
||||
|
||||
// Various things *only* apply to key down/up.
|
||||
BOOL eventIsARepeat = NO;
|
||||
NSString* eventCharacters = nil;
|
||||
NSString* eventUnmodCharacters = nil;
|
||||
if (eventType == NSKeyDown || eventType == NSKeyUp) {
|
||||
eventIsARepeat = [event isARepeat];
|
||||
eventCharacters = [event characters];
|
||||
eventUnmodCharacters = [event charactersIgnoringModifiers];
|
||||
}
|
||||
|
||||
// This synthesis may be slightly imperfect: we provide nil for the context,
|
||||
// since I (viettrungluu) am sceptical that putting in the original context
|
||||
// (if one is given) is valid.
|
||||
return [NSEvent keyEventWithType:eventType
|
||||
location:eventLoc
|
||||
modifierFlags:[event modifierFlags]
|
||||
timestamp:[event timestamp]
|
||||
windowNumber:[window windowNumber]
|
||||
context:nil
|
||||
characters:eventCharacters
|
||||
charactersIgnoringModifiers:eventUnmodCharacters
|
||||
isARepeat:eventIsARepeat
|
||||
keyCode:[event keyCode]];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||
if (redispatchingEvent_)
|
||||
return NO;
|
||||
|
||||
// Give the web site a chance to handle the event. If it doesn't want to
|
||||
// handle it, it will call us back with one of the |handle*| methods above.
|
||||
NSResponder* r = [self firstResponder];
|
||||
if ([r conformsToProtocol:@protocol(RenderWidgetHostViewMacBase)])
|
||||
return [r performKeyEquivalent:event];
|
||||
|
||||
if ([super performKeyEquivalent:event])
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end // EventProcessingWindow
|
57
atom/browser/ui/file_dialog.h
Normal file
57
atom/browser/ui/file_dialog.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BROWSER_UI_FILE_DIALOG_H_
|
||||
#define BROWSER_UI_FILE_DIALOG_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/files/file_path.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindow;
|
||||
}
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
enum FileDialogProperty {
|
||||
FILE_DIALOG_OPEN_FILE = 1,
|
||||
FILE_DIALOG_OPEN_DIRECTORY = 2,
|
||||
FILE_DIALOG_MULTI_SELECTIONS = 4,
|
||||
FILE_DIALOG_CREATE_DIRECTORY = 8,
|
||||
};
|
||||
|
||||
typedef base::Callback<void(
|
||||
bool result, const std::vector<base::FilePath>& paths)> OpenDialogCallback;
|
||||
|
||||
typedef base::Callback<void(
|
||||
bool result, const base::FilePath& path)> SaveDialogCallback;
|
||||
|
||||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths);
|
||||
|
||||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback);
|
||||
|
||||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
base::FilePath* path);
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const SaveDialogCallback& callback);
|
||||
|
||||
} // namespace file_dialog
|
||||
|
||||
#endif // BROWSER_UI_FILE_DIALOG_H_
|
199
atom/browser/ui/file_dialog_gtk.cc
Normal file
199
atom/browser/ui/file_dialog_gtk.cc
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright (c) 2014 GitHub, Inc. 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/file_dialog.h"
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/file_util.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/gtk/gtk_util.h"
|
||||
#include "ui/base/gtk/gtk_signal.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
namespace {
|
||||
|
||||
class FileChooserDialog {
|
||||
public:
|
||||
FileChooserDialog(GtkFileChooserAction action,
|
||||
atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path) {
|
||||
const char* confirm_text = GTK_STOCK_OK;
|
||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||
confirm_text = GTK_STOCK_SAVE;
|
||||
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
|
||||
confirm_text = GTK_STOCK_OPEN;
|
||||
|
||||
GtkWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL;
|
||||
dialog_ = gtk_file_chooser_dialog_new(
|
||||
title.c_str(),
|
||||
window,
|
||||
action,
|
||||
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||||
confirm_text, GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
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);
|
||||
|
||||
// Set window-to-parent modality by adding the dialog to the same window
|
||||
// group as the parent.
|
||||
gtk_window_group_add_window(gtk_window_get_group(window),
|
||||
GTK_WINDOW(dialog_));
|
||||
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
|
||||
|
||||
if (!default_path.empty()) {
|
||||
if (base::DirectoryExists(default_path))
|
||||
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog_),
|
||||
default_path.value().c_str());
|
||||
else
|
||||
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog_),
|
||||
default_path.value().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~FileChooserDialog() {
|
||||
gtk_widget_destroy(dialog_);
|
||||
}
|
||||
|
||||
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_);
|
||||
}
|
||||
|
||||
void RunSaveAsynchronous(const SaveDialogCallback& callback) {
|
||||
save_callback_ = callback;
|
||||
RunAsynchronous();
|
||||
}
|
||||
|
||||
void RunOpenAsynchronous(const OpenDialogCallback& callback) {
|
||||
open_callback_ = callback;
|
||||
RunAsynchronous();
|
||||
}
|
||||
|
||||
base::FilePath GetFileName() const {
|
||||
gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog_));
|
||||
base::FilePath path(filename);
|
||||
g_free(filename);
|
||||
return path;
|
||||
}
|
||||
|
||||
std::vector<base::FilePath> GetFileNames() const {
|
||||
std::vector<base::FilePath> paths;
|
||||
GSList* filenames = gtk_file_chooser_get_filenames(
|
||||
GTK_FILE_CHOOSER(dialog_));
|
||||
for (GSList* iter = filenames; iter != NULL; iter = g_slist_next(iter)) {
|
||||
base::FilePath path(static_cast<char*>(iter->data));
|
||||
g_free(iter->data);
|
||||
paths.push_back(path);
|
||||
}
|
||||
g_slist_free(filenames);
|
||||
return paths;
|
||||
}
|
||||
|
||||
CHROMEGTK_CALLBACK_1(FileChooserDialog, void, OnFileDialogResponse, int);
|
||||
|
||||
GtkWidget* dialog() const { return dialog_; }
|
||||
|
||||
private:
|
||||
GtkWidget* dialog_;
|
||||
|
||||
SaveDialogCallback save_callback_;
|
||||
OpenDialogCallback open_callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
|
||||
};
|
||||
|
||||
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
|
||||
gtk_widget_hide_all(dialog_);
|
||||
|
||||
if (!save_callback_.is_null()) {
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
save_callback_.Run(true, GetFileName());
|
||||
else
|
||||
save_callback_.Run(false, base::FilePath());
|
||||
} else if (!open_callback_.is_null()) {
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
open_callback_.Run(true, GetFileNames());
|
||||
else
|
||||
open_callback_.Run(false, std::vector<base::FilePath>());
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog open_dialog(action, parent_window, title, default_path);
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(open_dialog.dialog()),
|
||||
TRUE);
|
||||
|
||||
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;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback) {
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
FileChooserDialog* open_dialog = new FileChooserDialog(
|
||||
action, parent_window, title, default_path);
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
gtk_file_chooser_set_select_multiple(
|
||||
GTK_FILE_CHOOSER(open_dialog->dialog()), TRUE);
|
||||
|
||||
open_dialog->RunOpenAsynchronous(callback);
|
||||
}
|
||||
|
||||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
base::FilePath* path) {
|
||||
FileChooserDialog save_dialog(
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
|
||||
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;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const SaveDialogCallback& callback) {
|
||||
FileChooserDialog* save_dialog = new FileChooserDialog(
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE, parent_window, title, default_path);
|
||||
save_dialog->RunSaveAsynchronous(callback);
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
168
atom/browser/ui/file_dialog_mac.mm
Normal file
168
atom/browser/ui/file_dialog_mac.mm
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/file_dialog.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
namespace {
|
||||
|
||||
void SetupDialog(NSSavePanel* dialog,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path) {
|
||||
if (!title.empty())
|
||||
[dialog setTitle:base::SysUTF8ToNSString(title)];
|
||||
|
||||
NSString* default_dir = nil;
|
||||
NSString* default_filename = nil;
|
||||
if (!default_path.empty()) {
|
||||
if (base::DirectoryExists(default_path)) {
|
||||
default_dir = base::SysUTF8ToNSString(default_path.value());
|
||||
} else {
|
||||
default_dir = base::SysUTF8ToNSString(default_path.DirName().value());
|
||||
default_filename =
|
||||
base::SysUTF8ToNSString(default_path.BaseName().value());
|
||||
}
|
||||
}
|
||||
|
||||
if (default_dir)
|
||||
[dialog setDirectoryURL:[NSURL fileURLWithPath:default_dir]];
|
||||
if (default_filename)
|
||||
[dialog setNameFieldStringValue:default_filename];
|
||||
|
||||
[dialog setCanSelectHiddenExtension:YES];
|
||||
[dialog setAllowsOtherFileTypes:YES];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// Run modal dialog with parent window and return user's choice.
|
||||
int RunModalDialog(NSSavePanel* dialog, atom::NativeWindow* parent_window) {
|
||||
__block int chosen = NSFileHandlingPanelCancelButton;
|
||||
if (parent_window == NULL) {
|
||||
chosen = [dialog runModal];
|
||||
} else {
|
||||
NSWindow* window = parent_window->GetNativeWindow();
|
||||
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger c) {
|
||||
chosen = c;
|
||||
[NSApp stopModal];
|
||||
}];
|
||||
[NSApp runModalForWindow:window];
|
||||
}
|
||||
|
||||
return chosen;
|
||||
}
|
||||
|
||||
void ReadDialogPaths(NSOpenPanel* dialog, std::vector<base::FilePath>* paths) {
|
||||
NSArray* urls = [dialog URLs];
|
||||
for (NSURL* url in urls)
|
||||
if ([url isFileURL])
|
||||
paths->push_back(base::FilePath(base::SysNSStringToUTF8([url path])));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
DCHECK(paths);
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialogForProperties(dialog, properties);
|
||||
|
||||
int chosen = RunModalDialog(dialog, parent_window);
|
||||
if (chosen == NSFileHandlingPanelCancelButton)
|
||||
return false;
|
||||
|
||||
ReadDialogPaths(dialog, paths);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
const OpenDialogCallback& c) {
|
||||
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
SetupDialogForProperties(dialog, properties);
|
||||
|
||||
// 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 OpenDialogCallback callback = c;
|
||||
|
||||
NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL;
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
callback.Run(false, std::vector<base::FilePath>());
|
||||
} else {
|
||||
std::vector<base::FilePath> paths;
|
||||
ReadDialogPaths(dialog, &paths);
|
||||
callback.Run(true, paths);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
base::FilePath* path) {
|
||||
DCHECK(path);
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
|
||||
int chosen = RunModalDialog(dialog, parent_window);
|
||||
if (chosen == NSFileHandlingPanelCancelButton || ![[dialog URL] isFileURL])
|
||||
return false;
|
||||
|
||||
*path = base::FilePath(base::SysNSStringToUTF8([[dialog URL] path]));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const SaveDialogCallback& c) {
|
||||
NSSavePanel* dialog = [NSSavePanel savePanel];
|
||||
|
||||
SetupDialog(dialog, title, default_path);
|
||||
|
||||
__block SaveDialogCallback callback = c;
|
||||
|
||||
NSWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL;
|
||||
[dialog beginSheetModalForWindow:window
|
||||
completionHandler:^(NSInteger chosen) {
|
||||
if (chosen == NSFileHandlingPanelCancelButton) {
|
||||
callback.Run(false, base::FilePath());
|
||||
} else {
|
||||
std::string path = base::SysNSStringToUTF8([[dialog URL] path]);
|
||||
callback.Run(true, base::FilePath(path));
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
319
atom/browser/ui/file_dialog_win.cc
Normal file
319
atom/browser/ui/file_dialog_win.cc
Normal file
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/file_dialog.h"
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <windows.h>
|
||||
#include <commdlg.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/i18n/case_conversion.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/win/registry.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "third_party/wtl/include/atlapp.h"
|
||||
#include "third_party/wtl/include/atldlgs.h"
|
||||
|
||||
namespace file_dialog {
|
||||
|
||||
namespace {
|
||||
|
||||
// Distinguish directories from regular files.
|
||||
bool IsDirectory(const base::FilePath& path) {
|
||||
base::PlatformFileInfo file_info;
|
||||
return file_util::GetFileInfo(path, &file_info) ?
|
||||
file_info.is_directory : path.EndsWithSeparator();
|
||||
}
|
||||
|
||||
// Get the file type description from the registry. This will be "Text Document"
|
||||
// for .txt files, "JPEG Image" for .jpg files, etc. If the registry doesn't
|
||||
// have an entry for the file type, we return false, true if the description was
|
||||
// found. 'file_ext' must be in form ".txt".
|
||||
static bool GetRegistryDescriptionFromExtension(const std::wstring& file_ext,
|
||||
std::wstring* reg_description) {
|
||||
DCHECK(reg_description);
|
||||
base::win::RegKey reg_ext(HKEY_CLASSES_ROOT, file_ext.c_str(), KEY_READ);
|
||||
std::wstring reg_app;
|
||||
if (reg_ext.ReadValue(NULL, ®_app) == ERROR_SUCCESS && !reg_app.empty()) {
|
||||
base::win::RegKey reg_link(HKEY_CLASSES_ROOT, reg_app.c_str(), KEY_READ);
|
||||
if (reg_link.ReadValue(NULL, reg_description) == ERROR_SUCCESS)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up a filter for a Save/Open dialog, which will consist of |file_ext| file
|
||||
// extensions (internally separated by semicolons), |ext_desc| as the text
|
||||
// descriptions of the |file_ext| types (optional), and (optionally) the default
|
||||
// 'All Files' view. The purpose of the filter is to show only files of a
|
||||
// particular type in a Windows Save/Open dialog box. The resulting filter is
|
||||
// returned. The filters created here are:
|
||||
// 1. only files that have 'file_ext' as their extension
|
||||
// 2. all files (only added if 'include_all_files' is true)
|
||||
// Example:
|
||||
// file_ext: { "*.txt", "*.htm;*.html" }
|
||||
// ext_desc: { "Text Document" }
|
||||
// returned: "Text Document\0*.txt\0HTML Document\0*.htm;*.html\0"
|
||||
// "All Files\0*.*\0\0" (in one big string)
|
||||
// If a description is not provided for a file extension, it will be retrieved
|
||||
// from the registry. If the file extension does not exist in the registry, it
|
||||
// will be omitted from the filter, as it is likely a bogus extension.
|
||||
void FormatFilterForExtensions(
|
||||
std::vector<std::wstring>* file_ext,
|
||||
std::vector<std::wstring>* ext_desc,
|
||||
bool include_all_files,
|
||||
std::vector<COMDLG_FILTERSPEC>* file_types) {
|
||||
DCHECK(file_ext->size() >= ext_desc->size());
|
||||
|
||||
if (file_ext->empty())
|
||||
include_all_files = true;
|
||||
|
||||
for (size_t i = 0; i < file_ext->size(); ++i) {
|
||||
std::wstring ext = (*file_ext)[i];
|
||||
std::wstring desc;
|
||||
if (i < ext_desc->size())
|
||||
desc = (*ext_desc)[i];
|
||||
|
||||
if (ext.empty()) {
|
||||
// Force something reasonable to appear in the dialog box if there is no
|
||||
// extension provided.
|
||||
include_all_files = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desc.empty()) {
|
||||
DCHECK(ext.find(L'.') != std::wstring::npos);
|
||||
std::wstring first_extension = ext.substr(ext.find(L'.'));
|
||||
size_t first_separator_index = first_extension.find(L';');
|
||||
if (first_separator_index != std::wstring::npos)
|
||||
first_extension = first_extension.substr(0, first_separator_index);
|
||||
|
||||
// Find the extension name without the preceeding '.' character.
|
||||
std::wstring ext_name = first_extension;
|
||||
size_t ext_index = ext_name.find_first_not_of(L'.');
|
||||
if (ext_index != std::wstring::npos)
|
||||
ext_name = ext_name.substr(ext_index);
|
||||
|
||||
if (!GetRegistryDescriptionFromExtension(first_extension, &desc)) {
|
||||
// The extension doesn't exist in the registry. Create a description
|
||||
// based on the unknown extension type (i.e. if the extension is .qqq,
|
||||
// the we create a description "QQQ File (.qqq)").
|
||||
include_all_files = true;
|
||||
// TODO(zcbenz): should be localized.
|
||||
desc = base::i18n::ToUpper(WideToUTF16(ext_name)) + L" File";
|
||||
}
|
||||
desc += L" (*." + ext_name + L")";
|
||||
|
||||
// Store the description.
|
||||
ext_desc->push_back(desc);
|
||||
}
|
||||
|
||||
COMDLG_FILTERSPEC spec = { (*ext_desc)[i].c_str(), (*file_ext)[i].c_str() };
|
||||
file_types->push_back(spec);
|
||||
}
|
||||
|
||||
if (include_all_files) {
|
||||
// TODO(zcbenz): Should be localized.
|
||||
ext_desc->push_back(L"All Files (*.*)");
|
||||
file_ext->push_back(L"*.*");
|
||||
|
||||
COMDLG_FILTERSPEC spec = {
|
||||
(*ext_desc)[ext_desc->size() - 1].c_str(),
|
||||
(*file_ext)[file_ext->size() - 1].c_str(),
|
||||
};
|
||||
file_types->push_back(spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Generic class to delegate common open/save dialog's behaviours, users need to
|
||||
// call interface methods via GetPtr().
|
||||
template <typename T>
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog(const base::FilePath& default_path,
|
||||
const std::string title,
|
||||
int options,
|
||||
const std::vector<std::wstring>& file_ext,
|
||||
const std::vector<std::wstring>& desc_ext)
|
||||
: file_ext_(file_ext),
|
||||
desc_ext_(desc_ext) {
|
||||
std::vector<COMDLG_FILTERSPEC> filters;
|
||||
FormatFilterForExtensions(&file_ext_, &desc_ext_, true, &filters);
|
||||
|
||||
std::wstring file_part;
|
||||
if (!IsDirectory(default_path))
|
||||
file_part = default_path.BaseName().value();
|
||||
|
||||
dialog_.reset(new T(
|
||||
file_part.c_str(),
|
||||
options,
|
||||
NULL,
|
||||
filters.data(),
|
||||
filters.size()));
|
||||
|
||||
if (!title.empty())
|
||||
GetPtr()->SetTitle(UTF8ToUTF16(title).c_str());
|
||||
|
||||
SetDefaultFolder(default_path);
|
||||
}
|
||||
|
||||
bool Show(atom::NativeWindow* parent_window) {
|
||||
atom::NativeWindow::DialogScope dialog_scope(parent_window);
|
||||
HWND window = parent_window ? parent_window->GetNativeWindow() : NULL;
|
||||
return dialog_->DoModal(window) == IDOK;
|
||||
}
|
||||
|
||||
T* GetDialog() { return dialog_.get(); }
|
||||
|
||||
IFileDialog* GetPtr() const { return dialog_->GetPtr(); }
|
||||
|
||||
const std::vector<std::wstring> file_ext() const { return file_ext_; }
|
||||
|
||||
private:
|
||||
// Set up the initial directory for the dialog.
|
||||
void SetDefaultFolder(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))
|
||||
GetPtr()->SetDefaultFolder(folder_item);
|
||||
}
|
||||
|
||||
scoped_ptr<T> dialog_;
|
||||
|
||||
std::vector<std::wstring> file_ext_;
|
||||
std::vector<std::wstring> desc_ext_;
|
||||
std::vector<COMDLG_FILTERSPEC> filters_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileDialog);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
std::vector<base::FilePath>* paths) {
|
||||
int options = FOS_FORCEFILESYSTEM | FOS_FILEMUSTEXIST;
|
||||
if (properties & FILE_DIALOG_OPEN_DIRECTORY)
|
||||
options |= FOS_PICKFOLDERS;
|
||||
if (properties & FILE_DIALOG_MULTI_SELECTIONS)
|
||||
options |= FOS_ALLOWMULTISELECT;
|
||||
|
||||
FileDialog<CShellFileOpenDialog> open_dialog(
|
||||
default_path,
|
||||
title,
|
||||
options,
|
||||
std::vector<std::wstring>(),
|
||||
std::vector<std::wstring>());
|
||||
if (!open_dialog.Show(parent_window))
|
||||
return false;
|
||||
|
||||
ATL::CComPtr<IShellItemArray> items;
|
||||
HRESULT hr = static_cast<IFileOpenDialog*>(open_dialog.GetPtr())->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 = CShellFileOpenDialog::GetFileNameFromShellItem(
|
||||
item, SIGDN_FILESYSPATH, file_name, MAX_PATH);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
paths->push_back(base::FilePath(file_name));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowOpenDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
int properties,
|
||||
const OpenDialogCallback& callback) {
|
||||
std::vector<base::FilePath> paths;
|
||||
bool result = ShowOpenDialog(parent_window,
|
||||
title,
|
||||
default_path,
|
||||
properties,
|
||||
&paths);
|
||||
callback.Run(result, paths);
|
||||
}
|
||||
|
||||
bool ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
base::FilePath* path) {
|
||||
// TODO(zcbenz): Accept custom filters from caller.
|
||||
std::vector<std::wstring> file_ext;
|
||||
std::wstring extension = default_path.Extension();
|
||||
if (!extension.empty())
|
||||
file_ext.push_back(extension.insert(0, L"*"));
|
||||
|
||||
FileDialog<CShellFileSaveDialog> save_dialog(
|
||||
default_path,
|
||||
title,
|
||||
FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_OVERWRITEPROMPT,
|
||||
file_ext,
|
||||
std::vector<std::wstring>());
|
||||
if (!save_dialog.Show(parent_window))
|
||||
return false;
|
||||
|
||||
wchar_t file_name[MAX_PATH];
|
||||
HRESULT hr = save_dialog.GetDialog()->GetFilePath(file_name, MAX_PATH);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
// Append extension according to selected filter.
|
||||
UINT filter_index = 1;
|
||||
save_dialog.GetPtr()->GetFileTypeIndex(&filter_index);
|
||||
std::wstring selected_filter = save_dialog.file_ext()[filter_index - 1];
|
||||
if (selected_filter != L"*.*") {
|
||||
std::wstring result = file_name;
|
||||
if (!EndsWith(result, selected_filter.substr(1), false)) {
|
||||
if (result[result.length() - 1] != L'.')
|
||||
result.push_back(L'.');
|
||||
result.append(selected_filter.substr(2));
|
||||
*path = base::FilePath(result);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
*path = base::FilePath(file_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowSaveDialog(atom::NativeWindow* parent_window,
|
||||
const std::string& title,
|
||||
const base::FilePath& default_path,
|
||||
const SaveDialogCallback& callback) {
|
||||
base::FilePath path;
|
||||
bool result = ShowSaveDialog(parent_window, title, default_path, &path);
|
||||
callback.Run(result, path);
|
||||
}
|
||||
|
||||
} // namespace file_dialog
|
44
atom/browser/ui/gtk/event_utils.cc
Normal file
44
atom/browser/ui/gtk/event_utils.cc
Normal file
|
@ -0,0 +1,44 @@
|
|||
// 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/gtk/event_utils.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "ui/base/window_open_disposition.h"
|
||||
#include "ui/events/event_constants.h"
|
||||
|
||||
namespace event_utils {
|
||||
|
||||
int EventFlagsFromGdkState(guint state) {
|
||||
int flags = ui::EF_NONE;
|
||||
flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : ui::EF_NONE;
|
||||
flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_MOUSE_BUTTON : ui::EF_NONE;
|
||||
flags |= (state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_MOUSE_BUTTON
|
||||
: ui::EF_NONE;
|
||||
flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_MOUSE_BUTTON : ui::EF_NONE;
|
||||
return flags;
|
||||
}
|
||||
|
||||
// TODO(shinyak) This function will be removed after refactoring.
|
||||
WindowOpenDisposition DispositionFromGdkState(guint state) {
|
||||
int event_flags = EventFlagsFromGdkState(state);
|
||||
return ui::DispositionFromEventFlags(event_flags);
|
||||
}
|
||||
|
||||
WindowOpenDisposition DispositionForCurrentButtonPressEvent() {
|
||||
GdkEvent* event = gtk_get_current_event();
|
||||
if (!event) {
|
||||
NOTREACHED();
|
||||
return NEW_FOREGROUND_TAB;
|
||||
}
|
||||
|
||||
guint state = event->button.state;
|
||||
gdk_event_free(event);
|
||||
return DispositionFromGdkState(state);
|
||||
}
|
||||
|
||||
} // namespace event_utils
|
28
atom/browser/ui/gtk/event_utils.h
Normal file
28
atom/browser/ui/gtk/event_utils.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// 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 CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_
|
||||
#define CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "ui/base/window_open_disposition.h"
|
||||
|
||||
namespace event_utils {
|
||||
|
||||
// Translates event flags into plaform independent event flags.
|
||||
int EventFlagsFromGdkState(guint state);
|
||||
|
||||
// Translates GdkEvent state into what kind of disposition they represent.
|
||||
// For example, a middle click would mean to open a background tab.
|
||||
WindowOpenDisposition DispositionFromGdkState(guint state);
|
||||
|
||||
// Get the window open disposition from the state in gtk_get_current_event().
|
||||
// This is designed to be called inside a "clicked" event handler. It is an
|
||||
// error to call it when gtk_get_current_event() won't return a GdkEventButton*.
|
||||
WindowOpenDisposition DispositionForCurrentButtonPressEvent();
|
||||
|
||||
} // namespace event_utils
|
||||
|
||||
#endif // CHROME_BROWSER_UI_GTK_EVENT_UTILS_H_
|
150
atom/browser/ui/gtk/gtk_custom_menu.cc
Normal file
150
atom/browser/ui/gtk/gtk_custom_menu.cc
Normal file
|
@ -0,0 +1,150 @@
|
|||
// 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/gtk/gtk_custom_menu.h"
|
||||
|
||||
#include "atom/browser/ui/gtk/gtk_custom_menu_item.h"
|
||||
|
||||
G_DEFINE_TYPE(GtkCustomMenu, gtk_custom_menu, GTK_TYPE_MENU)
|
||||
|
||||
// Stolen directly from gtkmenushell.c. I'd love to call the library version
|
||||
// instead, but it's static and isn't exported. :(
|
||||
static gint gtk_menu_shell_is_item(GtkMenuShell* menu_shell,
|
||||
GtkWidget* child) {
|
||||
GtkWidget *parent;
|
||||
|
||||
g_return_val_if_fail(GTK_IS_MENU_SHELL(menu_shell), FALSE);
|
||||
g_return_val_if_fail(child != NULL, FALSE);
|
||||
|
||||
parent = gtk_widget_get_parent(child);
|
||||
while (GTK_IS_MENU_SHELL(parent)) {
|
||||
if (parent == reinterpret_cast<GtkWidget*>(menu_shell))
|
||||
return TRUE;
|
||||
parent = GTK_MENU_SHELL(parent)->parent_menu_shell;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Stolen directly from gtkmenushell.c. I'd love to call the library version
|
||||
// instead, but it's static and isn't exported. :(
|
||||
static GtkWidget* gtk_menu_shell_get_item(GtkMenuShell* menu_shell,
|
||||
GdkEvent* event) {
|
||||
GtkWidget* menu_item = gtk_get_event_widget(event);
|
||||
|
||||
while (menu_item && !GTK_IS_MENU_ITEM(menu_item))
|
||||
menu_item = gtk_widget_get_parent(menu_item);
|
||||
|
||||
if (menu_item && gtk_menu_shell_is_item(menu_shell, menu_item))
|
||||
return menu_item;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// When processing a button event, abort processing if the cursor isn't in a
|
||||
// clickable region.
|
||||
static gboolean gtk_custom_menu_button_press(GtkWidget* widget,
|
||||
GdkEventButton* event) {
|
||||
GtkWidget* menu_item = gtk_menu_shell_get_item(
|
||||
GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
|
||||
if (!gtk_custom_menu_item_is_in_clickable_region(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
|
||||
button_press_event(widget, event);
|
||||
}
|
||||
|
||||
// When processing a button event, abort processing if the cursor isn't in a
|
||||
// clickable region. If it's in a button that doesn't dismiss the menu, fire
|
||||
// that event and abort having the normal GtkMenu code run.
|
||||
static gboolean gtk_custom_menu_button_release(GtkWidget* widget,
|
||||
GdkEventButton* event) {
|
||||
GtkWidget* menu_item = gtk_menu_shell_get_item(
|
||||
GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
|
||||
if (!gtk_custom_menu_item_is_in_clickable_region(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item))) {
|
||||
// Stop processing this event. This isn't a clickable region.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (gtk_custom_menu_item_try_no_dismiss_command(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
|
||||
button_release_event(widget, event);
|
||||
}
|
||||
|
||||
// Manually forward button press events to the menu item (and then do what we'd
|
||||
// do normally).
|
||||
static gboolean gtk_custom_menu_motion_notify(GtkWidget* widget,
|
||||
GdkEventMotion* event) {
|
||||
GtkWidget* menu_item = gtk_menu_shell_get_item(
|
||||
GTK_MENU_SHELL(widget), (GdkEvent*)event);
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
|
||||
gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item),
|
||||
event->x, event->y);
|
||||
}
|
||||
|
||||
return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
|
||||
motion_notify_event(widget, event);
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_move_current(GtkMenuShell* menu_shell,
|
||||
GtkMenuDirectionType direction) {
|
||||
// If the currently selected item is custom, we give it first chance to catch
|
||||
// up/down events.
|
||||
|
||||
// TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need
|
||||
// to fix this by the time gtk3 comes out.
|
||||
GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
|
||||
switch (direction) {
|
||||
case GTK_MENU_DIR_PREV:
|
||||
case GTK_MENU_DIR_NEXT:
|
||||
if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item),
|
||||
direction))
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)->
|
||||
move_current(menu_shell, direction);
|
||||
|
||||
// In the case of hitting PREV and transitioning to a custom menu, we want to
|
||||
// make sure we're selecting the final item in the list, not the first one.
|
||||
menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
|
||||
gtk_custom_menu_item_select_item_by_direction(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item), direction);
|
||||
}
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_init(GtkCustomMenu* menu) {
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) {
|
||||
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
|
||||
GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass);
|
||||
|
||||
widget_class->button_press_event = gtk_custom_menu_button_press;
|
||||
widget_class->button_release_event = gtk_custom_menu_button_release;
|
||||
widget_class->motion_notify_event = gtk_custom_menu_motion_notify;
|
||||
|
||||
menu_shell_class->move_current = gtk_custom_menu_move_current;
|
||||
}
|
||||
|
||||
GtkWidget* gtk_custom_menu_new() {
|
||||
return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL));
|
||||
}
|
51
atom/browser/ui/gtk/gtk_custom_menu.h
Normal file
51
atom/browser/ui/gtk/gtk_custom_menu.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
// 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 file.
|
||||
|
||||
#ifndef CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_
|
||||
#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_
|
||||
|
||||
// GtkCustomMenu is a GtkMenu subclass that can contain, and collaborates with,
|
||||
// GtkCustomMenuItem instances. GtkCustomMenuItem is a GtkMenuItem that can
|
||||
// have buttons and other normal widgets embeded in it. GtkCustomMenu exists
|
||||
// only to override most of the button/motion/move callback functions so
|
||||
// that the normal GtkMenu implementation doesn't handle events related to
|
||||
// GtkCustomMenuItem items.
|
||||
//
|
||||
// For a more through overview of this system, see the comments in
|
||||
// gtk_custom_menu_item.h.
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_CUSTOM_MENU \
|
||||
(gtk_custom_menu_get_type())
|
||||
#define GTK_CUSTOM_MENU(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenu))
|
||||
#define GTK_CUSTOM_MENU_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass))
|
||||
#define GTK_IS_CUSTOM_MENU(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU))
|
||||
#define GTK_IS_CUSTOM_MENU_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU))
|
||||
#define GTK_CUSTOM_MENU_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU, GtkCustomMenuClass))
|
||||
|
||||
typedef struct _GtkCustomMenu GtkCustomMenu;
|
||||
typedef struct _GtkCustomMenuClass GtkCustomMenuClass;
|
||||
|
||||
struct _GtkCustomMenu {
|
||||
GtkMenu menu;
|
||||
};
|
||||
|
||||
struct _GtkCustomMenuClass {
|
||||
GtkMenuClass parent_class;
|
||||
};
|
||||
|
||||
GType gtk_custom_menu_get_type(void) G_GNUC_CONST;
|
||||
GtkWidget* gtk_custom_menu_new();
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_H_
|
493
atom/browser/ui/gtk/gtk_custom_menu_item.cc
Normal file
493
atom/browser/ui/gtk/gtk_custom_menu_item.cc
Normal file
|
@ -0,0 +1,493 @@
|
|||
// 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/gtk/gtk_custom_menu_item.h"
|
||||
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "atom/browser/ui/gtk/gtk_custom_menu.h"
|
||||
#include "ui/gfx/gtk_compat.h"
|
||||
|
||||
// This method was autogenerated by the program glib-genmarshall, which
|
||||
// generated it from the line "BOOL:INT". Two different attempts at getting gyp
|
||||
// to autogenerate this didn't work. If we need more non-standard marshallers,
|
||||
// this should be deleted, and an actual build step should be added.
|
||||
void chrome_marshall_BOOLEAN__INT(GClosure* closure,
|
||||
GValue* return_value G_GNUC_UNUSED,
|
||||
guint n_param_values,
|
||||
const GValue* param_values,
|
||||
gpointer invocation_hint G_GNUC_UNUSED,
|
||||
gpointer marshal_data) {
|
||||
typedef gboolean(*GMarshalFunc_BOOLEAN__INT)(gpointer data1,
|
||||
gint arg_1,
|
||||
gpointer data2);
|
||||
register GMarshalFunc_BOOLEAN__INT callback;
|
||||
register GCClosure *cc = (GCClosure*)closure;
|
||||
register gpointer data1, data2;
|
||||
gboolean v_return;
|
||||
|
||||
g_return_if_fail(return_value != NULL);
|
||||
g_return_if_fail(n_param_values == 2);
|
||||
|
||||
if (G_CCLOSURE_SWAP_DATA(closure)) {
|
||||
data1 = closure->data;
|
||||
// Note: This line (and the line setting data1 in the other if branch)
|
||||
// were macros in the original autogenerated output. This is with the
|
||||
// macro resolved for release mode. In debug mode, it uses an accessor
|
||||
// that asserts saying that the object pointed to by param_values doesn't
|
||||
// hold a pointer. This appears to be the cause of http://crbug.com/58945.
|
||||
//
|
||||
// This is more than a little odd because the gtype on this first param
|
||||
// isn't set correctly by the time we get here, while I watched it
|
||||
// explicitly set upstack. I verified that v_pointer is still set
|
||||
// correctly. I'm not sure what's going on. :(
|
||||
data2 = (param_values + 0)->data[0].v_pointer;
|
||||
} else {
|
||||
data1 = (param_values + 0)->data[0].v_pointer;
|
||||
data2 = closure->data;
|
||||
}
|
||||
callback = (GMarshalFunc_BOOLEAN__INT)(marshal_data ? marshal_data :
|
||||
cc->callback);
|
||||
|
||||
v_return = callback(data1,
|
||||
g_value_get_int(param_values + 1),
|
||||
data2);
|
||||
|
||||
g_value_set_boolean(return_value, v_return);
|
||||
}
|
||||
|
||||
enum {
|
||||
BUTTON_PUSHED,
|
||||
TRY_BUTTON_PUSHED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static guint custom_menu_item_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
G_DEFINE_TYPE(GtkCustomMenuItem, gtk_custom_menu_item, GTK_TYPE_MENU_ITEM)
|
||||
|
||||
static void set_selected(GtkCustomMenuItem* item, GtkWidget* selected) {
|
||||
if (selected != item->currently_selected_button) {
|
||||
if (item->currently_selected_button) {
|
||||
gtk_widget_set_state(item->currently_selected_button, GTK_STATE_NORMAL);
|
||||
gtk_widget_set_state(
|
||||
gtk_bin_get_child(GTK_BIN(item->currently_selected_button)),
|
||||
GTK_STATE_NORMAL);
|
||||
}
|
||||
|
||||
item->currently_selected_button = selected;
|
||||
if (item->currently_selected_button) {
|
||||
gtk_widget_set_state(item->currently_selected_button, GTK_STATE_SELECTED);
|
||||
gtk_widget_set_state(
|
||||
gtk_bin_get_child(GTK_BIN(item->currently_selected_button)),
|
||||
GTK_STATE_PRELIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When GtkButtons set the label text, they rebuild the widget hierarchy each
|
||||
// and every time. Therefore, we can't just fish out the label from the button
|
||||
// and set some properties; we have to create this callback function that
|
||||
// listens on the button's "notify" signal, which is emitted right after the
|
||||
// label has been (re)created. (Label values can change dynamically.)
|
||||
static void on_button_label_set(GObject* object) {
|
||||
GtkButton* button = GTK_BUTTON(object);
|
||||
GtkWidget* child = gtk_bin_get_child(GTK_BIN(button));
|
||||
gtk_widget_set_sensitive(child, FALSE);
|
||||
gtk_misc_set_padding(GTK_MISC(child), 2, 0);
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_finalize(GObject *object);
|
||||
static gint gtk_custom_menu_item_expose(GtkWidget* widget,
|
||||
GdkEventExpose* event);
|
||||
static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget,
|
||||
GdkEventExpose* event,
|
||||
GtkCustomMenuItem* menu_item);
|
||||
static void gtk_custom_menu_item_select(GtkItem *item);
|
||||
static void gtk_custom_menu_item_deselect(GtkItem *item);
|
||||
static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item);
|
||||
|
||||
static void gtk_custom_menu_item_init(GtkCustomMenuItem* item) {
|
||||
item->all_widgets = NULL;
|
||||
item->button_widgets = NULL;
|
||||
item->currently_selected_button = NULL;
|
||||
item->previously_selected_button = NULL;
|
||||
|
||||
GtkWidget* menu_hbox = gtk_hbox_new(FALSE, 0);
|
||||
gtk_container_add(GTK_CONTAINER(item), menu_hbox);
|
||||
|
||||
item->label = gtk_label_new(NULL);
|
||||
gtk_misc_set_alignment(GTK_MISC(item->label), 0.0, 0.5);
|
||||
gtk_box_pack_start(GTK_BOX(menu_hbox), item->label, TRUE, TRUE, 0);
|
||||
|
||||
item->hbox = gtk_hbox_new(FALSE, 0);
|
||||
gtk_box_pack_end(GTK_BOX(menu_hbox), item->hbox, FALSE, FALSE, 0);
|
||||
|
||||
g_signal_connect(item->hbox, "expose-event",
|
||||
G_CALLBACK(gtk_custom_menu_item_hbox_expose),
|
||||
item);
|
||||
|
||||
gtk_widget_show_all(menu_hbox);
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_class_init(GtkCustomMenuItemClass* klass) {
|
||||
GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
|
||||
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
|
||||
GtkItemClass* item_class = GTK_ITEM_CLASS(klass);
|
||||
GtkMenuItemClass* menu_item_class = GTK_MENU_ITEM_CLASS(klass);
|
||||
|
||||
gobject_class->finalize = gtk_custom_menu_item_finalize;
|
||||
|
||||
widget_class->expose_event = gtk_custom_menu_item_expose;
|
||||
|
||||
item_class->select = gtk_custom_menu_item_select;
|
||||
item_class->deselect = gtk_custom_menu_item_deselect;
|
||||
|
||||
menu_item_class->activate = gtk_custom_menu_item_activate;
|
||||
|
||||
custom_menu_item_signals[BUTTON_PUSHED] =
|
||||
g_signal_new("button-pushed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__INT,
|
||||
G_TYPE_NONE, 1, G_TYPE_INT);
|
||||
custom_menu_item_signals[TRY_BUTTON_PUSHED] =
|
||||
g_signal_new("try-button-pushed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL,
|
||||
chrome_marshall_BOOLEAN__INT,
|
||||
G_TYPE_BOOLEAN, 1, G_TYPE_INT);
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_finalize(GObject *object) {
|
||||
GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(object);
|
||||
g_list_free(item->all_widgets);
|
||||
g_list_free(item->button_widgets);
|
||||
|
||||
G_OBJECT_CLASS(gtk_custom_menu_item_parent_class)->finalize(object);
|
||||
}
|
||||
|
||||
static gint gtk_custom_menu_item_expose(GtkWidget* widget,
|
||||
GdkEventExpose* event) {
|
||||
if (gtk_widget_get_visible(widget) &&
|
||||
gtk_widget_get_mapped(widget) &&
|
||||
gtk_bin_get_child(GTK_BIN(widget))) {
|
||||
// We skip the drawing in the GtkMenuItem class it draws the highlighted
|
||||
// background and we don't want that.
|
||||
gtk_container_propagate_expose(GTK_CONTAINER(widget),
|
||||
gtk_bin_get_child(GTK_BIN(widget)),
|
||||
event);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_expose_button(GtkWidget* hbox,
|
||||
GdkEventExpose* event,
|
||||
GList* button_item) {
|
||||
// We search backwards to find the leftmost and rightmost buttons. The
|
||||
// current button may be that button.
|
||||
GtkWidget* current_button = GTK_WIDGET(button_item->data);
|
||||
GtkWidget* first_button = current_button;
|
||||
for (GList* i = button_item; i && GTK_IS_BUTTON(i->data);
|
||||
i = g_list_previous(i)) {
|
||||
first_button = GTK_WIDGET(i->data);
|
||||
}
|
||||
|
||||
GtkWidget* last_button = current_button;
|
||||
for (GList* i = button_item; i && GTK_IS_BUTTON(i->data);
|
||||
i = g_list_next(i)) {
|
||||
last_button = GTK_WIDGET(i->data);
|
||||
}
|
||||
|
||||
if (base::i18n::IsRTL())
|
||||
std::swap(first_button, last_button);
|
||||
|
||||
GtkAllocation first_allocation;
|
||||
gtk_widget_get_allocation(first_button, &first_allocation);
|
||||
GtkAllocation current_allocation;
|
||||
gtk_widget_get_allocation(current_button, ¤t_allocation);
|
||||
GtkAllocation last_allocation;
|
||||
gtk_widget_get_allocation(last_button, &last_allocation);
|
||||
|
||||
int x = first_allocation.x;
|
||||
int y = first_allocation.y;
|
||||
int width = last_allocation.width + last_allocation.x - first_allocation.x;
|
||||
int height = last_allocation.height;
|
||||
|
||||
gtk_paint_box(gtk_widget_get_style(hbox),
|
||||
gtk_widget_get_window(hbox),
|
||||
gtk_widget_get_state(current_button),
|
||||
GTK_SHADOW_OUT,
|
||||
¤t_allocation, hbox, "button",
|
||||
x, y, width, height);
|
||||
|
||||
// Propagate to the button's children.
|
||||
gtk_container_propagate_expose(
|
||||
GTK_CONTAINER(current_button),
|
||||
gtk_bin_get_child(GTK_BIN(current_button)),
|
||||
event);
|
||||
}
|
||||
|
||||
static gboolean gtk_custom_menu_item_hbox_expose(GtkWidget* widget,
|
||||
GdkEventExpose* event,
|
||||
GtkCustomMenuItem* menu_item) {
|
||||
// First render all the buttons that aren't the currently selected item.
|
||||
for (GList* current_item = menu_item->all_widgets;
|
||||
current_item != NULL; current_item = g_list_next(current_item)) {
|
||||
if (GTK_IS_BUTTON(current_item->data)) {
|
||||
if (GTK_WIDGET(current_item->data) !=
|
||||
menu_item->currently_selected_button) {
|
||||
gtk_custom_menu_item_expose_button(widget, event, current_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As a separate pass, draw the buton separators above. We need to draw the
|
||||
// separators in a separate pass because we are drawing on top of the
|
||||
// buttons. Otherwise, the vlines are overwritten by the next button.
|
||||
for (GList* current_item = menu_item->all_widgets;
|
||||
current_item != NULL; current_item = g_list_next(current_item)) {
|
||||
if (GTK_IS_BUTTON(current_item->data)) {
|
||||
// Check to see if this is the last button in a run.
|
||||
GList* next_item = g_list_next(current_item);
|
||||
if (next_item && GTK_IS_BUTTON(next_item->data)) {
|
||||
GtkWidget* current_button = GTK_WIDGET(current_item->data);
|
||||
GtkAllocation button_allocation;
|
||||
gtk_widget_get_allocation(current_button, &button_allocation);
|
||||
GtkAllocation child_alloc;
|
||||
gtk_widget_get_allocation(gtk_bin_get_child(GTK_BIN(current_button)),
|
||||
&child_alloc);
|
||||
GtkStyle* style = gtk_widget_get_style(widget);
|
||||
int half_offset = style->xthickness / 2;
|
||||
gtk_paint_vline(style,
|
||||
gtk_widget_get_window(widget),
|
||||
gtk_widget_get_state(current_button),
|
||||
&event->area, widget, "button",
|
||||
child_alloc.y,
|
||||
child_alloc.y + child_alloc.height,
|
||||
button_allocation.x +
|
||||
button_allocation.width - half_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, draw the selected item on top of the separators so there are no
|
||||
// artifacts inside the button area.
|
||||
GList* selected = g_list_find(menu_item->all_widgets,
|
||||
menu_item->currently_selected_button);
|
||||
if (selected) {
|
||||
gtk_custom_menu_item_expose_button(widget, event, selected);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_select(GtkItem* item) {
|
||||
GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item);
|
||||
|
||||
// When we are selected, the only thing we do is clear information from
|
||||
// previous selections. Actual selection of a button is done either in the
|
||||
// "mouse-motion-event" or is manually set from GtkCustomMenu's overridden
|
||||
// "move-current" handler.
|
||||
custom_item->previously_selected_button = NULL;
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(item));
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_deselect(GtkItem* item) {
|
||||
GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(item);
|
||||
|
||||
// When we are deselected, we store the item that was currently selected so
|
||||
// that it can be acted on. Menu items are first deselected before they are
|
||||
// activated.
|
||||
custom_item->previously_selected_button =
|
||||
custom_item->currently_selected_button;
|
||||
if (custom_item->currently_selected_button)
|
||||
set_selected(custom_item, NULL);
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(item));
|
||||
}
|
||||
|
||||
static void gtk_custom_menu_item_activate(GtkMenuItem* menu_item) {
|
||||
GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item);
|
||||
|
||||
// We look at |previously_selected_button| because by the time we've been
|
||||
// activated, we've already gone through our deselect handler.
|
||||
if (custom_item->previously_selected_button) {
|
||||
gpointer id_ptr = g_object_get_data(
|
||||
G_OBJECT(custom_item->previously_selected_button), "command-id");
|
||||
if (id_ptr != NULL) {
|
||||
int command_id = GPOINTER_TO_INT(id_ptr);
|
||||
g_signal_emit(custom_item, custom_menu_item_signals[BUTTON_PUSHED], 0,
|
||||
command_id);
|
||||
set_selected(custom_item, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget* gtk_custom_menu_item_new(const char* title) {
|
||||
GtkCustomMenuItem* item = GTK_CUSTOM_MENU_ITEM(
|
||||
g_object_new(GTK_TYPE_CUSTOM_MENU_ITEM, NULL));
|
||||
gtk_label_set_text(GTK_LABEL(item->label), title);
|
||||
return GTK_WIDGET(item);
|
||||
}
|
||||
|
||||
GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item,
|
||||
int command_id) {
|
||||
GtkWidget* button = gtk_button_new();
|
||||
g_object_set_data(G_OBJECT(button), "command-id",
|
||||
GINT_TO_POINTER(command_id));
|
||||
gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0);
|
||||
gtk_widget_show(button);
|
||||
|
||||
menu_item->all_widgets = g_list_append(menu_item->all_widgets, button);
|
||||
menu_item->button_widgets = g_list_append(menu_item->button_widgets, button);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item,
|
||||
int command_id) {
|
||||
GtkWidget* button = gtk_button_new_with_label("");
|
||||
g_object_set_data(G_OBJECT(button), "command-id",
|
||||
GINT_TO_POINTER(command_id));
|
||||
gtk_box_pack_start(GTK_BOX(menu_item->hbox), button, FALSE, FALSE, 0);
|
||||
g_signal_connect(button, "notify::label",
|
||||
G_CALLBACK(on_button_label_set), NULL);
|
||||
gtk_widget_show(button);
|
||||
|
||||
menu_item->all_widgets = g_list_append(menu_item->all_widgets, button);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item) {
|
||||
GtkWidget* fixed = gtk_fixed_new();
|
||||
gtk_widget_set_size_request(fixed, 5, -1);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(menu_item->hbox), fixed, FALSE, FALSE, 0);
|
||||
gtk_widget_show(fixed);
|
||||
|
||||
menu_item->all_widgets = g_list_append(menu_item->all_widgets, fixed);
|
||||
}
|
||||
|
||||
void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item,
|
||||
gdouble x, gdouble y) {
|
||||
GtkWidget* new_selected_widget = NULL;
|
||||
GList* current = menu_item->button_widgets;
|
||||
for (; current != NULL; current = current->next) {
|
||||
GtkWidget* current_widget = GTK_WIDGET(current->data);
|
||||
GtkAllocation alloc;
|
||||
gtk_widget_get_allocation(current_widget, &alloc);
|
||||
int offset_x, offset_y;
|
||||
gtk_widget_translate_coordinates(current_widget, GTK_WIDGET(menu_item),
|
||||
0, 0, &offset_x, &offset_y);
|
||||
if (x >= offset_x && x < (offset_x + alloc.width) &&
|
||||
y >= offset_y && y < (offset_y + alloc.height)) {
|
||||
new_selected_widget = current_widget;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
set_selected(menu_item, new_selected_widget);
|
||||
}
|
||||
|
||||
gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item,
|
||||
GtkMenuDirectionType direction) {
|
||||
GtkWidget* current = menu_item->currently_selected_button;
|
||||
if (menu_item->button_widgets && current) {
|
||||
switch (direction) {
|
||||
case GTK_MENU_DIR_PREV: {
|
||||
if (g_list_first(menu_item->button_widgets)->data == current)
|
||||
return FALSE;
|
||||
|
||||
set_selected(menu_item, GTK_WIDGET(g_list_previous(g_list_find(
|
||||
menu_item->button_widgets, current))->data));
|
||||
break;
|
||||
}
|
||||
case GTK_MENU_DIR_NEXT: {
|
||||
if (g_list_last(menu_item->button_widgets)->data == current)
|
||||
return FALSE;
|
||||
|
||||
set_selected(menu_item, GTK_WIDGET(g_list_next(g_list_find(
|
||||
menu_item->button_widgets, current))->data));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void gtk_custom_menu_item_select_item_by_direction(
|
||||
GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction) {
|
||||
menu_item->previously_selected_button = NULL;
|
||||
|
||||
// If we're just told to be selected by the menu system, select the first
|
||||
// item.
|
||||
if (menu_item->button_widgets) {
|
||||
switch (direction) {
|
||||
case GTK_MENU_DIR_PREV: {
|
||||
GtkWidget* last_button =
|
||||
GTK_WIDGET(g_list_last(menu_item->button_widgets)->data);
|
||||
if (last_button)
|
||||
set_selected(menu_item, last_button);
|
||||
break;
|
||||
}
|
||||
case GTK_MENU_DIR_NEXT: {
|
||||
GtkWidget* first_button =
|
||||
GTK_WIDGET(g_list_first(menu_item->button_widgets)->data);
|
||||
if (first_button)
|
||||
set_selected(menu_item, first_button);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(menu_item));
|
||||
}
|
||||
|
||||
gboolean gtk_custom_menu_item_is_in_clickable_region(
|
||||
GtkCustomMenuItem* menu_item) {
|
||||
return menu_item->currently_selected_button != NULL;
|
||||
}
|
||||
|
||||
gboolean gtk_custom_menu_item_try_no_dismiss_command(
|
||||
GtkCustomMenuItem* menu_item) {
|
||||
GtkCustomMenuItem* custom_item = GTK_CUSTOM_MENU_ITEM(menu_item);
|
||||
gboolean activated = TRUE;
|
||||
|
||||
// We work with |currently_selected_button| instead of
|
||||
// |previously_selected_button| since we haven't been "deselect"ed yet.
|
||||
gpointer id_ptr = g_object_get_data(
|
||||
G_OBJECT(custom_item->currently_selected_button), "command-id");
|
||||
if (id_ptr != NULL) {
|
||||
int command_id = GPOINTER_TO_INT(id_ptr);
|
||||
g_signal_emit(custom_item, custom_menu_item_signals[TRY_BUTTON_PUSHED], 0,
|
||||
command_id, &activated);
|
||||
}
|
||||
|
||||
return activated;
|
||||
}
|
||||
|
||||
void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item,
|
||||
GtkCallback callback,
|
||||
gpointer callback_data) {
|
||||
// Even though we're filtering |all_widgets| on GTK_IS_BUTTON(), this isn't
|
||||
// equivalent to |button_widgets| because we also want the button-labels.
|
||||
for (GList* i = menu_item->all_widgets; i && GTK_IS_BUTTON(i->data);
|
||||
i = g_list_next(i)) {
|
||||
if (GTK_IS_BUTTON(i->data)) {
|
||||
callback(GTK_WIDGET(i->data), callback_data);
|
||||
}
|
||||
}
|
||||
}
|
139
atom/browser/ui/gtk/gtk_custom_menu_item.h
Normal file
139
atom/browser/ui/gtk/gtk_custom_menu_item.h
Normal file
|
@ -0,0 +1,139 @@
|
|||
// 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 file.
|
||||
|
||||
#ifndef CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_
|
||||
#define CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_
|
||||
|
||||
// GtkCustomMenuItem is a GtkMenuItem subclass that has buttons in it and acts
|
||||
// to support this. GtkCustomMenuItems only render properly when put in a
|
||||
// GtkCustomMenu; there's a lot of collaboration between these two classes
|
||||
// necessary to work around how gtk normally does menus.
|
||||
//
|
||||
// We can't rely on the normal event infrastructure. While a menu is up, the
|
||||
// GtkMenu has a grab on all events. Instead of trying to pump events through
|
||||
// the normal channels, we have the GtkCustomMenu selectively forward mouse
|
||||
// motion through a back channel. The GtkCustomMenu only listens for button
|
||||
// press information so it can block the effects of the click if the cursor
|
||||
// isn't in a button in the menu item.
|
||||
//
|
||||
// A GtkCustomMenuItem doesn't try to take these signals and forward them to
|
||||
// the buttons it owns. The GtkCustomMenu class keeps track of which button is
|
||||
// selected (due to key events and mouse movement) and otherwise acts like a
|
||||
// normal GtkItem. The buttons are only for sizing and rendering; they don't
|
||||
// respond to events. Instead, when the GtkCustomMenuItem is activated by the
|
||||
// GtkMenu, it uses which button was selected as a signal of what to do.
|
||||
//
|
||||
// Users should connect to the "button-pushed" signal to be notified when a
|
||||
// button was pushed. We don't go through the normal "activate" signal because
|
||||
// we need to communicate additional information, namely which button was
|
||||
// activated.
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_CUSTOM_MENU_ITEM \
|
||||
(gtk_custom_menu_item_get_type())
|
||||
#define GTK_CUSTOM_MENU_ITEM(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \
|
||||
GtkCustomMenuItem))
|
||||
#define GTK_CUSTOM_MENU_ITEM_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_CUSTOM_MENU_ITEM, \
|
||||
GtkCustomMenuItemClass))
|
||||
#define GTK_IS_CUSTOM_MENU_ITEM(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_CUSTOM_MENU_ITEM))
|
||||
#define GTK_IS_CUSTOM_MENU_ITEM_CLASS(klass) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_CUSTOM_MENU_ITEM))
|
||||
#define GTK_CUSTOM_MENU_ITEM_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_CUSTOM_MENU_ITEM, \
|
||||
GtkCustomMenuItemClass))
|
||||
|
||||
typedef struct _GtkCustomMenuItem GtkCustomMenuItem;
|
||||
typedef struct _GtkCustomMenuItemClass GtkCustomMenuItemClass;
|
||||
|
||||
struct _GtkCustomMenuItem {
|
||||
GtkMenuItem menu_item;
|
||||
|
||||
// Container for button widgets.
|
||||
GtkWidget* hbox;
|
||||
|
||||
// Label on left side of menu item.
|
||||
GtkWidget* label;
|
||||
|
||||
// List of all widgets we added. Used to find the leftmost and rightmost
|
||||
// continuous buttons.
|
||||
GList* all_widgets;
|
||||
|
||||
// Possible button widgets. Used for keyboard navigation.
|
||||
GList* button_widgets;
|
||||
|
||||
// The widget that currently has highlight.
|
||||
GtkWidget* currently_selected_button;
|
||||
|
||||
// The widget that was selected *before* |currently_selected_button|. Why do
|
||||
// we hang on to this? Because the menu system sends us a deselect signal
|
||||
// right before activating us. We need to listen to deselect since that's
|
||||
// what we receive when the mouse cursor leaves us entirely.
|
||||
GtkWidget* previously_selected_button;
|
||||
};
|
||||
|
||||
struct _GtkCustomMenuItemClass {
|
||||
GtkMenuItemClass parent_class;
|
||||
};
|
||||
|
||||
GType gtk_custom_menu_item_get_type(void) G_GNUC_CONST;
|
||||
GtkWidget* gtk_custom_menu_item_new(const char* title);
|
||||
|
||||
// Adds a button to our list of items in the |hbox|.
|
||||
GtkWidget* gtk_custom_menu_item_add_button(GtkCustomMenuItem* menu_item,
|
||||
int command_id);
|
||||
|
||||
// Adds a button to our list of items in the |hbox|, but that isn't part of
|
||||
// |button_widgets| to prevent it from being activatable.
|
||||
GtkWidget* gtk_custom_menu_item_add_button_label(GtkCustomMenuItem* menu_item,
|
||||
int command_id);
|
||||
|
||||
// Adds a vertical space in the |hbox|.
|
||||
void gtk_custom_menu_item_add_space(GtkCustomMenuItem* menu_item);
|
||||
|
||||
// Receives a motion event from the GtkCustomMenu that contains us. We can't
|
||||
// just subscribe to motion-event or the individual widget enter/leave events
|
||||
// because the top level GtkMenu has an event grab.
|
||||
void gtk_custom_menu_item_receive_motion_event(GtkCustomMenuItem* menu_item,
|
||||
gdouble x, gdouble y);
|
||||
|
||||
// Notification that the menu got a cursor key event. Used to move up/down
|
||||
// within the menu buttons. Returns TRUE to stop the default signal handler
|
||||
// from running.
|
||||
gboolean gtk_custom_menu_item_handle_move(GtkCustomMenuItem* menu_item,
|
||||
GtkMenuDirectionType direction);
|
||||
|
||||
// Because we only get a generic "selected" signal when we've changed, we need
|
||||
// to have a way for the GtkCustomMenu to tell us that we were just
|
||||
// selected.
|
||||
void gtk_custom_menu_item_select_item_by_direction(
|
||||
GtkCustomMenuItem* menu_item, GtkMenuDirectionType direction);
|
||||
|
||||
// Whether we are currently hovering over a clickable region on the menu
|
||||
// item. Used by GtkCustomMenu to determine whether it should discard click
|
||||
// events.
|
||||
gboolean gtk_custom_menu_item_is_in_clickable_region(
|
||||
GtkCustomMenuItem* menu_item);
|
||||
|
||||
// If the button is released while the |currently_selected_button| isn't
|
||||
// supposed to dismiss the menu, this signals to our listeners that we want to
|
||||
// run this command if it doesn't dismiss the menu. Returns TRUE if we acted
|
||||
// on this button click (and should prevent the normal GtkMenu machinery from
|
||||
// firing an "activate" signal).
|
||||
gboolean gtk_custom_menu_item_try_no_dismiss_command(
|
||||
GtkCustomMenuItem* menu_item);
|
||||
|
||||
// Calls |callback| with every button and button-label in the container.
|
||||
void gtk_custom_menu_item_foreach_button(GtkCustomMenuItem* menu_item,
|
||||
GtkCallback callback,
|
||||
gpointer callback_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // CHROME_BROWSER_UI_GTK_GTK_CUSTOM_MENU_ITEM_H_
|
94
atom/browser/ui/gtk/gtk_util.cc
Normal file
94
atom/browser/ui/gtk/gtk_util.cc
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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/gtk/gtk_util.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace gtk_util {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";
|
||||
|
||||
// Returns the approximate number of characters that can horizontally fit in
|
||||
// |pixel_width| pixels.
|
||||
int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) {
|
||||
DCHECK(gtk_widget_get_realized(widget))
|
||||
<< " widget must be realized to compute font metrics correctly";
|
||||
|
||||
PangoContext* context = gtk_widget_create_pango_context(widget);
|
||||
GtkStyle* style = gtk_widget_get_style(widget);
|
||||
PangoFontMetrics* metrics = pango_context_get_metrics(context,
|
||||
style->font_desc, pango_context_get_language(context));
|
||||
|
||||
// This technique (max of char and digit widths) matches the code in
|
||||
// gtklabel.c.
|
||||
int char_width = pixel_width * PANGO_SCALE /
|
||||
std::max(pango_font_metrics_get_approximate_char_width(metrics),
|
||||
pango_font_metrics_get_approximate_digit_width(metrics));
|
||||
|
||||
pango_font_metrics_unref(metrics);
|
||||
g_object_unref(context);
|
||||
|
||||
return char_width;
|
||||
}
|
||||
|
||||
void OnLabelRealize(GtkWidget* label, gpointer pixel_width) {
|
||||
gtk_label_set_width_chars(
|
||||
GTK_LABEL(label),
|
||||
GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GtkWidget* LeftAlignMisc(GtkWidget* misc) {
|
||||
gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5);
|
||||
return misc;
|
||||
}
|
||||
|
||||
GtkWidget* CreateBoldLabel(const std::string& text) {
|
||||
GtkWidget* label = gtk_label_new(NULL);
|
||||
char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str());
|
||||
gtk_label_set_markup(GTK_LABEL(label), markup);
|
||||
g_free(markup);
|
||||
|
||||
return LeftAlignMisc(label);
|
||||
}
|
||||
|
||||
void SetAlwaysShowImage(GtkWidget* image_menu_item) {
|
||||
gtk_image_menu_item_set_always_show_image(
|
||||
GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE);
|
||||
}
|
||||
|
||||
bool IsWidgetAncestryVisible(GtkWidget* widget) {
|
||||
GtkWidget* parent = widget;
|
||||
while (parent && gtk_widget_get_visible(parent))
|
||||
parent = gtk_widget_get_parent(parent);
|
||||
return !parent;
|
||||
}
|
||||
|
||||
void SetLabelWidth(GtkWidget* label, int pixel_width) {
|
||||
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
|
||||
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
|
||||
|
||||
// Do the simple thing in LTR because the bug only affects right-aligned
|
||||
// text. Also, when using the workaround, the label tries to maintain
|
||||
// uniform line-length, which we don't really want.
|
||||
if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) {
|
||||
gtk_widget_set_size_request(label, pixel_width, -1);
|
||||
} else {
|
||||
// The label has to be realized before we can adjust its width.
|
||||
if (gtk_widget_get_realized(label)) {
|
||||
OnLabelRealize(label, GINT_TO_POINTER(pixel_width));
|
||||
} else {
|
||||
g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize),
|
||||
GINT_TO_POINTER(pixel_width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gtk_util
|
36
atom/browser/ui/gtk/gtk_util.h
Normal file
36
atom/browser/ui/gtk/gtk_util.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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_GTK_GTK_UTIL_H_
|
||||
#define ATOM_BROWSER_UI_GTK_GTK_UTIL_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <string>
|
||||
|
||||
namespace gtk_util {
|
||||
|
||||
// Left-align the given GtkMisc and return the same pointer.
|
||||
GtkWidget* LeftAlignMisc(GtkWidget* misc);
|
||||
|
||||
// Create a left-aligned label with the given text in bold.
|
||||
GtkWidget* CreateBoldLabel(const std::string& text);
|
||||
|
||||
// Show the image for the given menu item, even if the user's default is to not
|
||||
// show images. Only to be used for favicons or other menus where the image is
|
||||
// crucial to its functionality.
|
||||
void SetAlwaysShowImage(GtkWidget* image_menu_item);
|
||||
|
||||
// Checks whether a widget is actually visible, i.e. whether it and all its
|
||||
// ancestors up to its toplevel are visible.
|
||||
bool IsWidgetAncestryVisible(GtkWidget* widget);
|
||||
|
||||
// Sets the given label's size request to |pixel_width|. This will cause the
|
||||
// label to wrap if it needs to. The reason for this function is that some
|
||||
// versions of GTK mis-align labels that have a size request and line wrapping,
|
||||
// and this function hides the complexity of the workaround.
|
||||
void SetLabelWidth(GtkWidget* label, int pixel_width);
|
||||
|
||||
} // namespace gtk_util
|
||||
|
||||
#endif // ATOM_BROWSER_UI_GTK_GTK_UTIL_H_
|
295
atom/browser/ui/gtk/gtk_window_util.cc
Normal file
295
atom/browser/ui/gtk/gtk_window_util.cc
Normal file
|
@ -0,0 +1,295 @@
|
|||
// 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/gtk/gtk_window_util.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include "content/public/browser/render_view_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_view.h"
|
||||
|
||||
using content::RenderWidgetHost;
|
||||
using content::WebContents;
|
||||
|
||||
namespace gtk_window_util {
|
||||
|
||||
const int kFrameBorderThickness = 4;
|
||||
const int kResizeAreaCornerSize = 16;
|
||||
|
||||
// Keep track of the last click time and the last click position so we can
|
||||
// filter out extra GDK_BUTTON_PRESS events when a double click happens.
|
||||
static guint32 last_click_time;
|
||||
static int last_click_x;
|
||||
static int last_click_y;
|
||||
|
||||
// Performs Cut/Copy/Paste operation on the |window|.
|
||||
// If the current render view is focused, then just call the specified |method|
|
||||
// against the current render view host, otherwise emit the specified |signal|
|
||||
// against the focused widget.
|
||||
// TODO(suzhe): This approach does not work for plugins.
|
||||
void DoCutCopyPaste(GtkWindow* window,
|
||||
WebContents* web_contents,
|
||||
void (RenderWidgetHost::*method)(),
|
||||
const char* signal) {
|
||||
GtkWidget* widget = gtk_window_get_focus(window);
|
||||
if (widget == NULL)
|
||||
return; // Do nothing if no focused widget.
|
||||
|
||||
if (web_contents &&
|
||||
widget == web_contents->GetView()->GetContentNativeView()) {
|
||||
(web_contents->GetRenderViewHost()->*method)();
|
||||
} else {
|
||||
guint id;
|
||||
if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0)
|
||||
g_signal_emit(widget, id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void DoCut(GtkWindow* window, WebContents* web_contents) {
|
||||
DoCutCopyPaste(window, web_contents,
|
||||
&RenderWidgetHost::Cut, "cut-clipboard");
|
||||
}
|
||||
|
||||
void DoCopy(GtkWindow* window, WebContents* web_contents) {
|
||||
DoCutCopyPaste(window, web_contents,
|
||||
&RenderWidgetHost::Copy, "copy-clipboard");
|
||||
}
|
||||
|
||||
void DoPaste(GtkWindow* window, WebContents* web_contents) {
|
||||
DoCutCopyPaste(window, web_contents,
|
||||
&RenderWidgetHost::Paste, "paste-clipboard");
|
||||
}
|
||||
|
||||
// Ubuntu patches their version of GTK+ so that there is always a
|
||||
// gripper in the bottom right corner of the window. We dynamically
|
||||
// look up this symbol because it's a non-standard Ubuntu extension to
|
||||
// GTK+. We always need to disable this feature since we can't
|
||||
// communicate this to WebKit easily.
|
||||
typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean);
|
||||
gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym;
|
||||
|
||||
void DisableResizeGrip(GtkWindow* window) {
|
||||
static bool resize_grip_looked_up = false;
|
||||
if (!resize_grip_looked_up) {
|
||||
resize_grip_looked_up = true;
|
||||
gtk_window_set_has_resize_grip_sym =
|
||||
reinterpret_cast<gtk_window_set_has_resize_grip_func>(
|
||||
dlsym(NULL, "gtk_window_set_has_resize_grip"));
|
||||
}
|
||||
if (gtk_window_set_has_resize_grip_sym)
|
||||
gtk_window_set_has_resize_grip_sym(window, FALSE);
|
||||
}
|
||||
|
||||
GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) {
|
||||
switch (edge) {
|
||||
case GDK_WINDOW_EDGE_NORTH_WEST:
|
||||
return GDK_TOP_LEFT_CORNER;
|
||||
case GDK_WINDOW_EDGE_NORTH:
|
||||
return GDK_TOP_SIDE;
|
||||
case GDK_WINDOW_EDGE_NORTH_EAST:
|
||||
return GDK_TOP_RIGHT_CORNER;
|
||||
case GDK_WINDOW_EDGE_WEST:
|
||||
return GDK_LEFT_SIDE;
|
||||
case GDK_WINDOW_EDGE_EAST:
|
||||
return GDK_RIGHT_SIDE;
|
||||
case GDK_WINDOW_EDGE_SOUTH_WEST:
|
||||
return GDK_BOTTOM_LEFT_CORNER;
|
||||
case GDK_WINDOW_EDGE_SOUTH:
|
||||
return GDK_BOTTOM_SIDE;
|
||||
case GDK_WINDOW_EDGE_SOUTH_EAST:
|
||||
return GDK_BOTTOM_RIGHT_CORNER;
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
return GDK_LAST_CURSOR;
|
||||
}
|
||||
|
||||
bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds) {
|
||||
// A screen can be composed of multiple monitors.
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
GdkRectangle monitor_size;
|
||||
|
||||
if (gtk_widget_get_realized(GTK_WIDGET(window))) {
|
||||
// |window| has been realized.
|
||||
gint monitor_num = gdk_screen_get_monitor_at_window(screen,
|
||||
gtk_widget_get_window(GTK_WIDGET(window)));
|
||||
gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size);
|
||||
return bounds.size() == gfx::Size(monitor_size.width, monitor_size.height);
|
||||
}
|
||||
|
||||
// Make sure the window doesn't match any monitor size. We compare against
|
||||
// all monitors because we don't know which monitor the window is going to
|
||||
// open on before window realized.
|
||||
gint num_monitors = gdk_screen_get_n_monitors(screen);
|
||||
for (gint i = 0; i < num_monitors; ++i) {
|
||||
GdkRectangle monitor_size;
|
||||
gdk_screen_get_monitor_geometry(screen, i, &monitor_size);
|
||||
if (bounds.size() == gfx::Size(monitor_size.width, monitor_size.height))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandleTitleBarLeftMousePress(
|
||||
GtkWindow* window,
|
||||
const gfx::Rect& bounds,
|
||||
GdkEventButton* event) {
|
||||
// We want to start a move when the user single clicks, but not start a
|
||||
// move when the user double clicks. However, a double click sends the
|
||||
// following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE,
|
||||
// GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we
|
||||
// start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS,
|
||||
// the call to gtk_window_maximize fails. To work around this, we
|
||||
// keep track of the last click and if it's going to be a double click,
|
||||
// we don't call gtk_window_begin_move_drag.
|
||||
DCHECK_EQ(event->type, GDK_BUTTON_PRESS);
|
||||
DCHECK_EQ(event->button, 1);
|
||||
|
||||
static GtkSettings* settings = gtk_settings_get_default();
|
||||
gint double_click_time = 250;
|
||||
gint double_click_distance = 5;
|
||||
g_object_get(G_OBJECT(settings),
|
||||
"gtk-double-click-time", &double_click_time,
|
||||
"gtk-double-click-distance", &double_click_distance,
|
||||
NULL);
|
||||
|
||||
guint32 click_time = event->time - last_click_time;
|
||||
int click_move_x = abs(event->x - last_click_x);
|
||||
int click_move_y = abs(event->y - last_click_y);
|
||||
|
||||
last_click_time = event->time;
|
||||
last_click_x = static_cast<int>(event->x);
|
||||
last_click_y = static_cast<int>(event->y);
|
||||
|
||||
if (click_time > static_cast<guint32>(double_click_time) ||
|
||||
click_move_x > double_click_distance ||
|
||||
click_move_y > double_click_distance) {
|
||||
// Ignore drag requests if the window is the size of the screen.
|
||||
// We do this to avoid triggering fullscreen mode in metacity
|
||||
// (without the --no-force-fullscreen flag) and in compiz (with
|
||||
// Legacy Fullscreen Mode enabled).
|
||||
if (!BoundsMatchMonitorSize(window, bounds)) {
|
||||
gtk_window_begin_move_drag(window, event->button,
|
||||
static_cast<gint>(event->x_root),
|
||||
static_cast<gint>(event->y_root),
|
||||
event->time);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void UnMaximize(GtkWindow* window,
|
||||
const gfx::Rect& bounds,
|
||||
const gfx::Rect& restored_bounds) {
|
||||
gtk_window_unmaximize(window);
|
||||
|
||||
// It can happen that you end up with a window whose restore size is the same
|
||||
// as the size of the screen, so unmaximizing it merely remaximizes it due to
|
||||
// the same WM feature that SetWindowSize() works around. We try to detect
|
||||
// this and resize the window to work around the issue.
|
||||
if (bounds.size() == restored_bounds.size())
|
||||
gtk_window_resize(window, bounds.width(), bounds.height() - 1);
|
||||
}
|
||||
|
||||
void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass) {
|
||||
gtk_window_set_wmclass(window,
|
||||
wmclass.c_str(),
|
||||
gdk_get_program_class());
|
||||
|
||||
// Set WM_WINDOW_ROLE for session management purposes.
|
||||
// See http://tronche.com/gui/x/icccm/sec-5.html .
|
||||
gtk_window_set_role(window, wmclass.c_str());
|
||||
}
|
||||
|
||||
void SetWindowSize(GtkWindow* window, const gfx::Size& size) {
|
||||
gfx::Size new_size = size;
|
||||
gint current_width = 0;
|
||||
gint current_height = 0;
|
||||
gtk_window_get_size(window, ¤t_width, ¤t_height);
|
||||
GdkRectangle size_with_decorations = {0};
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
if (gdk_window) {
|
||||
gdk_window_get_frame_extents(gdk_window,
|
||||
&size_with_decorations);
|
||||
}
|
||||
|
||||
if (current_width == size_with_decorations.width &&
|
||||
current_height == size_with_decorations.height) {
|
||||
// Make sure the window doesn't match any monitor size. We compare against
|
||||
// all monitors because we don't know which monitor the window is going to
|
||||
// open on (the WM decides that).
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
gint num_monitors = gdk_screen_get_n_monitors(screen);
|
||||
for (gint i = 0; i < num_monitors; ++i) {
|
||||
GdkRectangle monitor_size;
|
||||
gdk_screen_get_monitor_geometry(screen, i, &monitor_size);
|
||||
if (gfx::Size(monitor_size.width, monitor_size.height) == size) {
|
||||
gtk_window_resize(window, size.width(), size.height() - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// gtk_window_resize is the size of the window not including decorations,
|
||||
// but we are given the |size| including window decorations.
|
||||
if (size_with_decorations.width > current_width) {
|
||||
new_size.set_width(size.width() - size_with_decorations.width +
|
||||
current_width);
|
||||
}
|
||||
if (size_with_decorations.height > current_height) {
|
||||
new_size.set_height(size.height() - size_with_decorations.height +
|
||||
current_height);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_window_resize(window, new_size.width(), new_size.height());
|
||||
}
|
||||
|
||||
bool GetWindowEdge(const gfx::Size& window_size,
|
||||
int top_edge_inset,
|
||||
int x,
|
||||
int y,
|
||||
GdkWindowEdge* edge) {
|
||||
gfx::Rect middle(window_size);
|
||||
middle.Inset(kFrameBorderThickness,
|
||||
kFrameBorderThickness - top_edge_inset,
|
||||
kFrameBorderThickness,
|
||||
kFrameBorderThickness);
|
||||
if (middle.Contains(x, y))
|
||||
return false;
|
||||
|
||||
gfx::Rect north(0, 0, window_size.width(),
|
||||
kResizeAreaCornerSize - top_edge_inset);
|
||||
gfx::Rect west(0, 0, kResizeAreaCornerSize, window_size.height());
|
||||
gfx::Rect south(0, window_size.height() - kResizeAreaCornerSize,
|
||||
window_size.width(), kResizeAreaCornerSize);
|
||||
gfx::Rect east(window_size.width() - kResizeAreaCornerSize, 0,
|
||||
kResizeAreaCornerSize, window_size.height());
|
||||
|
||||
if (north.Contains(x, y)) {
|
||||
if (west.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_NORTH_WEST;
|
||||
else if (east.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_NORTH_EAST;
|
||||
else
|
||||
*edge = GDK_WINDOW_EDGE_NORTH;
|
||||
} else if (south.Contains(x, y)) {
|
||||
if (west.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_SOUTH_WEST;
|
||||
else if (east.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_SOUTH_EAST;
|
||||
else
|
||||
*edge = GDK_WINDOW_EDGE_SOUTH;
|
||||
} else {
|
||||
if (west.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_WEST;
|
||||
else if (east.Contains(x, y))
|
||||
*edge = GDK_WINDOW_EDGE_EAST;
|
||||
else
|
||||
return false; // The cursor must be outside the window.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gtk_window_util
|
74
atom/browser/ui/gtk/gtk_window_util.h
Normal file
74
atom/browser/ui/gtk/gtk_window_util.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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_GTK_GTK_WINDOW_UTIL_H_
|
||||
#define ATOM_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include "ui/gfx/rect.h"
|
||||
|
||||
namespace content {
|
||||
class WebContents;
|
||||
}
|
||||
|
||||
namespace gtk_window_util {
|
||||
|
||||
// The frame border is only visible in restored mode and is hardcoded to 4 px
|
||||
// on each side regardless of the system window border size.
|
||||
extern const int kFrameBorderThickness;
|
||||
// In the window corners, the resize areas don't actually expand bigger, but
|
||||
// the 16 px at the end of each edge triggers diagonal resizing.
|
||||
extern const int kResizeAreaCornerSize;
|
||||
|
||||
// Performs Cut/Copy/Paste operation on the |window|'s |web_contents|.
|
||||
void DoCut(GtkWindow* window, content::WebContents* web_contents);
|
||||
void DoCopy(GtkWindow* window, content::WebContents* web_contents);
|
||||
void DoPaste(GtkWindow* window, content::WebContents* web_contents);
|
||||
|
||||
// Ubuntu patches their version of GTK+ to that there is always a
|
||||
// gripper in the bottom right corner of the window. We always need to
|
||||
// disable this feature since we can't communicate this to WebKit easily.
|
||||
void DisableResizeGrip(GtkWindow* window);
|
||||
|
||||
// Returns the resize cursor corresponding to the window |edge|.
|
||||
GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge);
|
||||
|
||||
// Returns |true| if the window bounds match the monitor size.
|
||||
bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds);
|
||||
|
||||
bool HandleTitleBarLeftMousePress(GtkWindow* window,
|
||||
const gfx::Rect& bounds,
|
||||
GdkEventButton* event);
|
||||
|
||||
// Request the underlying window to unmaximize. Also tries to work around
|
||||
// a window manager "feature" that can prevent this in some edge cases.
|
||||
void UnMaximize(GtkWindow* window,
|
||||
const gfx::Rect& bounds,
|
||||
const gfx::Rect& restored_bounds);
|
||||
|
||||
// Set a custom WM_CLASS for a window.
|
||||
void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass);
|
||||
|
||||
// A helper method for setting the GtkWindow size that should be used in place
|
||||
// of calling gtk_window_resize directly. This is done to avoid a WM "feature"
|
||||
// where setting the window size to the monitor size causes the WM to set the
|
||||
// EWMH for full screen mode.
|
||||
void SetWindowSize(GtkWindow* window, const gfx::Size& size);
|
||||
|
||||
// If the point (|x|, |y|) is within the resize border area of the window,
|
||||
// returns true and sets |edge| to the appropriate GdkWindowEdge value.
|
||||
// Otherwise, returns false.
|
||||
// |top_edge_inset| specifies how much smaller (in px) than the default edge
|
||||
// size the top edge should be, used by browser windows to make it easier to
|
||||
// move the window since a lot of title bar space is taken by the tabs.
|
||||
bool GetWindowEdge(const gfx::Size& window_size,
|
||||
int top_edge_inset,
|
||||
int x,
|
||||
int y,
|
||||
GdkWindowEdge* edge);
|
||||
|
||||
} // namespace gtk_window_util
|
||||
|
||||
#endif // ATOM_BROWSER_UI_GTK_GTK_WINDOW_UTIL_H_
|
879
atom/browser/ui/gtk/menu_gtk.cc
Normal file
879
atom/browser/ui/gtk/menu_gtk.cc
Normal file
|
@ -0,0 +1,879 @@
|
|||
// 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/gtk/menu_gtk.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "atom/browser/ui/gtk/event_utils.h"
|
||||
#include "atom/browser/ui/gtk/gtk_custom_menu.h"
|
||||
#include "atom/browser/ui/gtk/gtk_custom_menu_item.h"
|
||||
#include "atom/browser/ui/gtk/gtk_util.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
|
||||
#include "ui/base/accelerators/platform_accelerator_gtk.h"
|
||||
#include "ui/base/models/button_menu_item_model.h"
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/base/window_open_disposition.h"
|
||||
#include "ui/gfx/gtk_util.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
bool MenuGtk::block_activation_ = false;
|
||||
|
||||
namespace {
|
||||
|
||||
// Sets the ID of a menu item.
|
||||
void SetMenuItemID(GtkWidget* menu_item, int menu_id) {
|
||||
DCHECK_GE(menu_id, 0);
|
||||
|
||||
// Add 1 to the menu_id to avoid setting zero (null) to "menu-id".
|
||||
g_object_set_data(G_OBJECT(menu_item), "menu-id",
|
||||
GINT_TO_POINTER(menu_id + 1));
|
||||
}
|
||||
|
||||
// Gets the ID of a menu item.
|
||||
// Returns true if the menu item has an ID.
|
||||
bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) {
|
||||
gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id");
|
||||
if (id_ptr != NULL) {
|
||||
*menu_id = GPOINTER_TO_INT(id_ptr) - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) {
|
||||
return reinterpret_cast<ui::MenuModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "model"));
|
||||
}
|
||||
|
||||
void SetUpButtonShowHandler(GtkWidget* button,
|
||||
ui::ButtonMenuItemModel* model,
|
||||
int index) {
|
||||
g_object_set_data(G_OBJECT(button), "button-model",
|
||||
model);
|
||||
g_object_set_data(G_OBJECT(button), "button-model-id",
|
||||
GINT_TO_POINTER(index));
|
||||
}
|
||||
|
||||
void OnSubmenuShowButtonImage(GtkWidget* widget, GtkButton* button) {
|
||||
MenuGtk::Delegate* delegate = reinterpret_cast<MenuGtk::Delegate*>(
|
||||
g_object_get_data(G_OBJECT(button), "menu-gtk-delegate"));
|
||||
int icon_idr = GPOINTER_TO_INT(g_object_get_data(
|
||||
G_OBJECT(button), "button-image-idr"));
|
||||
|
||||
GtkIconSet* icon_set = delegate->GetIconSetForId(icon_idr);
|
||||
if (icon_set) {
|
||||
gtk_button_set_image(
|
||||
button, gtk_image_new_from_icon_set(icon_set,
|
||||
GTK_ICON_SIZE_MENU));
|
||||
}
|
||||
}
|
||||
|
||||
void SetupImageIcon(GtkWidget* button,
|
||||
GtkWidget* menu,
|
||||
int icon_idr,
|
||||
MenuGtk::Delegate* menu_gtk_delegate) {
|
||||
g_object_set_data(G_OBJECT(button), "button-image-idr",
|
||||
GINT_TO_POINTER(icon_idr));
|
||||
g_object_set_data(G_OBJECT(button), "menu-gtk-delegate",
|
||||
menu_gtk_delegate);
|
||||
|
||||
g_signal_connect(menu, "show", G_CALLBACK(OnSubmenuShowButtonImage), button);
|
||||
}
|
||||
|
||||
// Popup menus may get squished if they open up too close to the bottom of the
|
||||
// screen. This function takes the size of the screen, the size of the menu,
|
||||
// an optional widget, the Y position of the mouse click, and adjusts the popup
|
||||
// menu's Y position to make it fit if it's possible to do so.
|
||||
// Returns the new Y position of the popup menu.
|
||||
int CalculateMenuYPosition(const GdkRectangle* screen_rect,
|
||||
const GtkRequisition* menu_req,
|
||||
GtkWidget* widget, const int y) {
|
||||
CHECK(screen_rect);
|
||||
CHECK(menu_req);
|
||||
// If the menu would run off the bottom of the screen, and there is enough
|
||||
// screen space upwards to accommodate the menu, then pop upwards. If there
|
||||
// is a widget, then also move the anchor point to the top of the widget
|
||||
// rather than the bottom.
|
||||
const int screen_top = screen_rect->y;
|
||||
const int screen_bottom = screen_rect->y + screen_rect->height;
|
||||
const int menu_bottom = y + menu_req->height;
|
||||
int alternate_y = y - menu_req->height;
|
||||
if (widget) {
|
||||
GtkAllocation allocation;
|
||||
gtk_widget_get_allocation(widget, &allocation);
|
||||
alternate_y -= allocation.height;
|
||||
}
|
||||
if (menu_bottom >= screen_bottom && alternate_y >= screen_top)
|
||||
return alternate_y;
|
||||
return y;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool MenuGtk::Delegate::AlwaysShowIconForCmd(int command_id) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkIconSet* MenuGtk::Delegate::GetIconSetForId(int idr) { return NULL; }
|
||||
|
||||
GtkWidget* MenuGtk::Delegate::GetDefaultImageForLabel(
|
||||
const std::string& label) {
|
||||
const char* stock = NULL;
|
||||
if (label == "New")
|
||||
stock = GTK_STOCK_NEW;
|
||||
else if (label == "Close")
|
||||
stock = GTK_STOCK_CLOSE;
|
||||
else if (label == "Save As")
|
||||
stock = GTK_STOCK_SAVE_AS;
|
||||
else if (label == "Save")
|
||||
stock = GTK_STOCK_SAVE;
|
||||
else if (label == "Copy")
|
||||
stock = GTK_STOCK_COPY;
|
||||
else if (label == "Cut")
|
||||
stock = GTK_STOCK_CUT;
|
||||
else if (label == "Paste")
|
||||
stock = GTK_STOCK_PASTE;
|
||||
else if (label == "Delete")
|
||||
stock = GTK_STOCK_DELETE;
|
||||
else if (label == "Undo")
|
||||
stock = GTK_STOCK_UNDO;
|
||||
else if (label == "Redo")
|
||||
stock = GTK_STOCK_REDO;
|
||||
else if (label == "Search" || label == "Find")
|
||||
stock = GTK_STOCK_FIND;
|
||||
else if (label == "Select All")
|
||||
stock = GTK_STOCK_SELECT_ALL;
|
||||
else if (label == "Clear")
|
||||
stock = GTK_STOCK_SELECT_ALL;
|
||||
else if (label == "Back")
|
||||
stock = GTK_STOCK_GO_BACK;
|
||||
else if (label == "Forward")
|
||||
stock = GTK_STOCK_GO_FORWARD;
|
||||
else if (label == "Reload" || label == "Refresh")
|
||||
stock = GTK_STOCK_REFRESH;
|
||||
else if (label == "Print")
|
||||
stock = GTK_STOCK_PRINT;
|
||||
else if (label == "About")
|
||||
stock = GTK_STOCK_ABOUT;
|
||||
else if (label == "Quit")
|
||||
stock = GTK_STOCK_QUIT;
|
||||
else if (label == "Help")
|
||||
stock = GTK_STOCK_HELP;
|
||||
|
||||
return stock ? gtk_image_new_from_stock(stock, GTK_ICON_SIZE_MENU) : NULL;
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::Delegate::GetImageForCommandId(int command_id) const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuGtk::MenuGtk(MenuGtk::Delegate* delegate,
|
||||
ui::MenuModel* model,
|
||||
bool is_menubar)
|
||||
: delegate_(delegate),
|
||||
model_(model),
|
||||
is_menubar_(is_menubar),
|
||||
dummy_accel_group_(gtk_accel_group_new()),
|
||||
menu_(is_menubar ? gtk_menu_bar_new() : gtk_custom_menu_new()),
|
||||
weak_factory_(this) {
|
||||
DCHECK(model);
|
||||
g_object_ref_sink(menu_);
|
||||
ConnectSignalHandlers();
|
||||
BuildMenuFromModel();
|
||||
}
|
||||
|
||||
MenuGtk::~MenuGtk() {
|
||||
Cancel();
|
||||
|
||||
gtk_widget_destroy(menu_);
|
||||
g_object_unref(menu_);
|
||||
|
||||
g_object_unref(dummy_accel_group_);
|
||||
}
|
||||
|
||||
void MenuGtk::ConnectSignalHandlers() {
|
||||
// We connect afterwards because OnMenuShow calls SetMenuItemInfo, which may
|
||||
// take a long time or even start a nested message loop.
|
||||
g_signal_connect(menu_, "show", G_CALLBACK(OnMenuShowThunk), this);
|
||||
g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHiddenThunk), this);
|
||||
GtkWidget* toplevel_window = gtk_widget_get_toplevel(menu_);
|
||||
signal_.Connect(toplevel_window, "focus-out-event",
|
||||
G_CALLBACK(OnMenuFocusOutThunk), this);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendMenuItemWithLabel(int command_id,
|
||||
const std::string& label) {
|
||||
std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
|
||||
GtkWidget* menu_item = BuildMenuItemWithLabel(converted_label, command_id);
|
||||
return AppendMenuItem(command_id, menu_item);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendMenuItemWithIcon(int command_id,
|
||||
const std::string& label,
|
||||
const gfx::Image& icon) {
|
||||
std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
|
||||
GtkWidget* menu_item = BuildMenuItemWithImage(converted_label, icon);
|
||||
return AppendMenuItem(command_id, menu_item);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendCheckMenuItemWithLabel(int command_id,
|
||||
const std::string& label) {
|
||||
std::string converted_label = ui::ConvertAcceleratorsFromWindowsStyle(label);
|
||||
GtkWidget* menu_item =
|
||||
gtk_check_menu_item_new_with_mnemonic(converted_label.c_str());
|
||||
return AppendMenuItem(command_id, menu_item);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendSeparator() {
|
||||
GtkWidget* menu_item = gtk_separator_menu_item_new();
|
||||
gtk_widget_show(menu_item);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menu_), menu_item);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::InsertSeparator(int position) {
|
||||
GtkWidget* menu_item = gtk_separator_menu_item_new();
|
||||
gtk_widget_show(menu_item);
|
||||
gtk_menu_shell_insert(GTK_MENU_SHELL(menu_), menu_item, position);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendMenuItem(int command_id, GtkWidget* menu_item) {
|
||||
if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
|
||||
GTK_IS_IMAGE_MENU_ITEM(menu_item))
|
||||
gtk_util::SetAlwaysShowImage(menu_item);
|
||||
|
||||
return AppendMenuItemToMenu(command_id, NULL, menu_item, menu_, true);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::InsertMenuItem(int command_id, GtkWidget* menu_item,
|
||||
int position) {
|
||||
if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
|
||||
GTK_IS_IMAGE_MENU_ITEM(menu_item))
|
||||
gtk_util::SetAlwaysShowImage(menu_item);
|
||||
|
||||
return InsertMenuItemToMenu(command_id, NULL, menu_item, menu_, position,
|
||||
true);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::AppendMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
bool connect_to_activate) {
|
||||
int children_count = g_list_length(GTK_MENU_SHELL(menu)->children);
|
||||
return InsertMenuItemToMenu(index, model, menu_item, menu,
|
||||
children_count, connect_to_activate);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::InsertMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
int position,
|
||||
bool connect_to_activate) {
|
||||
SetMenuItemID(menu_item, index);
|
||||
|
||||
// Native menu items do their own thing, so only selectively listen for the
|
||||
// activate signal.
|
||||
if (connect_to_activate) {
|
||||
g_signal_connect(menu_item, "activate",
|
||||
G_CALLBACK(OnMenuItemActivatedThunk), this);
|
||||
}
|
||||
|
||||
// AppendMenuItemToMenu is used both internally when we control menu creation
|
||||
// from a model (where the model can choose to hide certain menu items), and
|
||||
// with immediate commands which don't provide the option.
|
||||
if (model) {
|
||||
if (model->IsVisibleAt(index))
|
||||
gtk_widget_show(menu_item);
|
||||
} else {
|
||||
gtk_widget_show(menu_item);
|
||||
}
|
||||
gtk_menu_shell_insert(GTK_MENU_SHELL(menu), menu_item, position);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
void MenuGtk::PopupForWidget(GtkWidget* widget, int button,
|
||||
guint32 event_time) {
|
||||
gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
|
||||
WidgetMenuPositionFunc,
|
||||
widget,
|
||||
button, event_time);
|
||||
}
|
||||
|
||||
void MenuGtk::PopupAsContext(const gfx::Point& point, guint32 event_time) {
|
||||
// gtk_menu_popup doesn't like the "const" qualifier on point.
|
||||
gfx::Point nonconst_point(point);
|
||||
gtk_menu_popup(GTK_MENU(menu_), NULL, NULL,
|
||||
PointMenuPositionFunc, &nonconst_point,
|
||||
3, event_time);
|
||||
}
|
||||
|
||||
void MenuGtk::PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
|
||||
GtkStatusIcon* icon) {
|
||||
gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, gtk_status_icon_position_menu,
|
||||
icon, button, event_time);
|
||||
}
|
||||
|
||||
void MenuGtk::PopupAsFromKeyEvent(GtkWidget* widget) {
|
||||
PopupForWidget(widget, 0, gtk_get_current_event_time());
|
||||
gtk_menu_shell_select_first(GTK_MENU_SHELL(menu_), FALSE);
|
||||
}
|
||||
|
||||
void MenuGtk::Cancel() {
|
||||
if (!is_menubar_)
|
||||
gtk_menu_popdown(GTK_MENU(menu_));
|
||||
}
|
||||
|
||||
void MenuGtk::UpdateMenu() {
|
||||
gtk_container_foreach(GTK_CONTAINER(menu_), SetMenuItemInfo, this);
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
|
||||
GtkWidget* image) {
|
||||
GtkWidget* menu_item =
|
||||
gtk_image_menu_item_new_with_mnemonic(label.c_str());
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image);
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::BuildMenuItemWithImage(const std::string& label,
|
||||
const gfx::Image& icon) {
|
||||
GtkWidget* menu_item = BuildMenuItemWithImage(label,
|
||||
gtk_image_new_from_pixbuf(icon.ToGdkPixbuf()));
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::BuildMenuItemWithLabel(const std::string& label,
|
||||
int command_id) {
|
||||
GtkWidget* img =
|
||||
delegate_ ? delegate_->GetImageForCommandId(command_id) :
|
||||
MenuGtk::Delegate::GetDefaultImageForLabel(label);
|
||||
return img ? BuildMenuItemWithImage(label, img) :
|
||||
gtk_menu_item_new_with_mnemonic(label.c_str());
|
||||
}
|
||||
|
||||
void MenuGtk::BuildMenuFromModel() {
|
||||
BuildSubmenuFromModel(model_, menu_);
|
||||
}
|
||||
|
||||
void MenuGtk::BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu) {
|
||||
std::map<int, GtkWidget*> radio_groups;
|
||||
GtkWidget* menu_item = NULL;
|
||||
for (int i = 0; i < model->GetItemCount(); ++i) {
|
||||
gfx::Image icon;
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(i)));
|
||||
bool connect_to_activate = true;
|
||||
|
||||
switch (model->GetTypeAt(i)) {
|
||||
case ui::MenuModel::TYPE_SEPARATOR:
|
||||
menu_item = gtk_separator_menu_item_new();
|
||||
break;
|
||||
|
||||
case ui::MenuModel::TYPE_CHECK:
|
||||
menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str());
|
||||
break;
|
||||
|
||||
case ui::MenuModel::TYPE_RADIO: {
|
||||
std::map<int, GtkWidget*>::iterator iter =
|
||||
radio_groups.find(model->GetGroupIdAt(i));
|
||||
|
||||
if (iter == radio_groups.end()) {
|
||||
menu_item = gtk_radio_menu_item_new_with_mnemonic(
|
||||
NULL, label.c_str());
|
||||
radio_groups[model->GetGroupIdAt(i)] = menu_item;
|
||||
} else {
|
||||
menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget(
|
||||
GTK_RADIO_MENU_ITEM(iter->second), label.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ui::MenuModel::TYPE_BUTTON_ITEM: {
|
||||
ui::ButtonMenuItemModel* button_menu_item_model =
|
||||
model->GetButtonMenuItemAt(i);
|
||||
menu_item = BuildButtonMenuItem(button_menu_item_model, menu);
|
||||
connect_to_activate = false;
|
||||
break;
|
||||
}
|
||||
case ui::MenuModel::TYPE_SUBMENU:
|
||||
case ui::MenuModel::TYPE_COMMAND: {
|
||||
int command_id = model->GetCommandIdAt(i);
|
||||
if (model->GetIconAt(i, &icon))
|
||||
menu_item = BuildMenuItemWithImage(label, icon);
|
||||
else
|
||||
menu_item = BuildMenuItemWithLabel(label, command_id);
|
||||
if (delegate_ && delegate_->AlwaysShowIconForCmd(command_id) &&
|
||||
GTK_IS_IMAGE_MENU_ITEM(menu_item)) {
|
||||
gtk_util::SetAlwaysShowImage(menu_item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) {
|
||||
GtkWidget* submenu = gtk_menu_new();
|
||||
g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item);
|
||||
ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i);
|
||||
g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model);
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
|
||||
// We will populate the submenu on demand when shown.
|
||||
g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this);
|
||||
g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this);
|
||||
connect_to_activate = false;
|
||||
}
|
||||
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAt(i, &accelerator)) {
|
||||
gtk_widget_add_accelerator(menu_item,
|
||||
"activate",
|
||||
dummy_accel_group_,
|
||||
ui::GetGdkKeyCodeForAccelerator(accelerator),
|
||||
ui::GetGdkModifierForAccelerator(accelerator),
|
||||
GTK_ACCEL_VISIBLE);
|
||||
}
|
||||
|
||||
g_object_set_data(G_OBJECT(menu_item), "model", model);
|
||||
AppendMenuItemToMenu(i, model, menu_item, menu, connect_to_activate);
|
||||
|
||||
menu_item = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget* MenuGtk::BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
|
||||
GtkWidget* menu) {
|
||||
GtkWidget* menu_item = gtk_custom_menu_item_new(
|
||||
ui::RemoveWindowsStyleAccelerators(
|
||||
base::UTF16ToUTF8(model->label())).c_str());
|
||||
|
||||
// Set up the callback to the model for when it is clicked.
|
||||
g_object_set_data(G_OBJECT(menu_item), "button-model", model);
|
||||
g_signal_connect(menu_item, "button-pushed",
|
||||
G_CALLBACK(OnMenuButtonPressedThunk), this);
|
||||
g_signal_connect(menu_item, "try-button-pushed",
|
||||
G_CALLBACK(OnMenuTryButtonPressedThunk), this);
|
||||
|
||||
GtkSizeGroup* group = NULL;
|
||||
for (int i = 0; i < model->GetItemCount(); ++i) {
|
||||
GtkWidget* button = NULL;
|
||||
|
||||
switch (model->GetTypeAt(i)) {
|
||||
case ui::ButtonMenuItemModel::TYPE_SPACE: {
|
||||
gtk_custom_menu_item_add_space(GTK_CUSTOM_MENU_ITEM(menu_item));
|
||||
break;
|
||||
}
|
||||
case ui::ButtonMenuItemModel::TYPE_BUTTON: {
|
||||
button = gtk_custom_menu_item_add_button(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item),
|
||||
model->GetCommandIdAt(i));
|
||||
|
||||
int icon_idr;
|
||||
if (model->GetIconAt(i, &icon_idr)) {
|
||||
SetupImageIcon(button, menu, icon_idr, delegate_);
|
||||
} else {
|
||||
gtk_button_set_label(
|
||||
GTK_BUTTON(button),
|
||||
ui::RemoveWindowsStyleAccelerators(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(i))).c_str());
|
||||
}
|
||||
|
||||
SetUpButtonShowHandler(button, model, i);
|
||||
break;
|
||||
}
|
||||
case ui::ButtonMenuItemModel::TYPE_BUTTON_LABEL: {
|
||||
button = gtk_custom_menu_item_add_button_label(
|
||||
GTK_CUSTOM_MENU_ITEM(menu_item),
|
||||
model->GetCommandIdAt(i));
|
||||
gtk_button_set_label(
|
||||
GTK_BUTTON(button),
|
||||
ui::RemoveWindowsStyleAccelerators(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(i))).c_str());
|
||||
SetUpButtonShowHandler(button, model, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (button && model->PartOfGroup(i)) {
|
||||
if (!group)
|
||||
group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
|
||||
|
||||
gtk_size_group_add_widget(group, button);
|
||||
}
|
||||
}
|
||||
|
||||
if (group)
|
||||
g_object_unref(group);
|
||||
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) {
|
||||
if (block_activation_)
|
||||
return;
|
||||
|
||||
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
|
||||
|
||||
if (!model) {
|
||||
// There won't be a model for "native" submenus like the "Input Methods"
|
||||
// context menu. We don't need to handle activation messages for submenus
|
||||
// anyway, so we can just return here.
|
||||
DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
|
||||
return;
|
||||
}
|
||||
|
||||
// The activate signal is sent to radio items as they get deselected;
|
||||
// ignore it in this case.
|
||||
if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
|
||||
!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
|
||||
return;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (!GetMenuItemID(menu_item, &id))
|
||||
return;
|
||||
|
||||
// The menu item can still be activated by hotkeys even if it is disabled.
|
||||
if (model->IsEnabledAt(id))
|
||||
ExecuteCommand(model, id);
|
||||
}
|
||||
|
||||
void MenuGtk::OnMenuButtonPressed(GtkWidget* menu_item, int command_id) {
|
||||
ui::ButtonMenuItemModel* model =
|
||||
reinterpret_cast<ui::ButtonMenuItemModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "button-model"));
|
||||
if (model && model->IsCommandIdEnabled(command_id)) {
|
||||
if (delegate_)
|
||||
delegate_->CommandWillBeExecuted();
|
||||
|
||||
model->ActivatedCommand(command_id);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean MenuGtk::OnMenuTryButtonPressed(GtkWidget* menu_item,
|
||||
int command_id) {
|
||||
gboolean pressed = FALSE;
|
||||
ui::ButtonMenuItemModel* model =
|
||||
reinterpret_cast<ui::ButtonMenuItemModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "button-model"));
|
||||
if (model &&
|
||||
model->IsCommandIdEnabled(command_id) &&
|
||||
!model->DoesCommandIdDismissMenu(command_id)) {
|
||||
if (delegate_)
|
||||
delegate_->CommandWillBeExecuted();
|
||||
|
||||
model->ActivatedCommand(command_id);
|
||||
pressed = TRUE;
|
||||
}
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
// static
|
||||
void MenuGtk::WidgetMenuPositionFunc(GtkMenu* menu,
|
||||
int* x,
|
||||
int* y,
|
||||
gboolean* push_in,
|
||||
void* void_widget) {
|
||||
GtkWidget* widget = GTK_WIDGET(void_widget);
|
||||
GtkRequisition menu_req;
|
||||
|
||||
gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
|
||||
|
||||
gdk_window_get_origin(gtk_widget_get_window(widget), x, y);
|
||||
GdkScreen *screen = gtk_widget_get_screen(widget);
|
||||
gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
|
||||
|
||||
GdkRectangle screen_rect;
|
||||
gdk_screen_get_monitor_geometry(screen, monitor,
|
||||
&screen_rect);
|
||||
|
||||
GtkAllocation allocation;
|
||||
gtk_widget_get_allocation(widget, &allocation);
|
||||
|
||||
if (!gtk_widget_get_has_window(widget)) {
|
||||
*x += allocation.x;
|
||||
*y += allocation.y;
|
||||
}
|
||||
*y += allocation.height;
|
||||
|
||||
bool start_align =
|
||||
!!g_object_get_data(G_OBJECT(widget), "left-align-popup");
|
||||
if (base::i18n::IsRTL())
|
||||
start_align = !start_align;
|
||||
|
||||
if (!start_align)
|
||||
*x += allocation.width - menu_req.width;
|
||||
|
||||
*y = CalculateMenuYPosition(&screen_rect, &menu_req, widget, *y);
|
||||
|
||||
*push_in = FALSE;
|
||||
}
|
||||
|
||||
// static
|
||||
void MenuGtk::PointMenuPositionFunc(GtkMenu* menu,
|
||||
int* x,
|
||||
int* y,
|
||||
gboolean* push_in,
|
||||
gpointer userdata) {
|
||||
*push_in = TRUE;
|
||||
|
||||
gfx::Point* point = reinterpret_cast<gfx::Point*>(userdata);
|
||||
*x = point->x();
|
||||
*y = point->y();
|
||||
|
||||
GtkRequisition menu_req;
|
||||
gtk_widget_size_request(GTK_WIDGET(menu), &menu_req);
|
||||
GdkScreen* screen;
|
||||
gdk_display_get_pointer(gdk_display_get_default(), &screen, NULL, NULL, NULL);
|
||||
gint monitor = gdk_screen_get_monitor_at_point(screen, *x, *y);
|
||||
|
||||
GdkRectangle screen_rect;
|
||||
gdk_screen_get_monitor_geometry(screen, monitor, &screen_rect);
|
||||
|
||||
*y = CalculateMenuYPosition(&screen_rect, &menu_req, NULL, *y);
|
||||
}
|
||||
|
||||
void MenuGtk::ExecuteCommand(ui::MenuModel* model, int id) {
|
||||
if (delegate_)
|
||||
delegate_->CommandWillBeExecuted();
|
||||
|
||||
GdkEvent* event = gtk_get_current_event();
|
||||
int event_flags = 0;
|
||||
|
||||
if (event && event->type == GDK_BUTTON_RELEASE)
|
||||
event_flags = event_utils::EventFlagsFromGdkState(event->button.state);
|
||||
model->ActivatedAt(id, event_flags);
|
||||
|
||||
if (event)
|
||||
gdk_event_free(event);
|
||||
}
|
||||
|
||||
void MenuGtk::OnMenuShow(GtkWidget* widget) {
|
||||
model_->MenuWillShow();
|
||||
base::MessageLoop::current()->PostTask(
|
||||
FROM_HERE, base::Bind(&MenuGtk::UpdateMenu, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void MenuGtk::OnMenuHidden(GtkWidget* widget) {
|
||||
if (delegate_)
|
||||
delegate_->StoppedShowing();
|
||||
model_->MenuClosed();
|
||||
}
|
||||
|
||||
gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) {
|
||||
gtk_widget_hide(menu_);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void MenuGtk::OnSubMenuShow(GtkWidget* submenu) {
|
||||
GtkWidget* menu_item = static_cast<GtkWidget*>(
|
||||
g_object_get_data(G_OBJECT(submenu), "menu-item"));
|
||||
// TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974.
|
||||
CHECK(menu_item);
|
||||
// Notify the submenu model that the menu will be shown.
|
||||
ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
|
||||
// We're extra cautious here, and bail out if the submenu model is NULL. In
|
||||
// some cases we clear it out from a parent menu; we shouldn't ever show the
|
||||
// menu after that, but we play it safe since we're dealing with wacky
|
||||
// injected libraries that toy with our menus. (See comments below.)
|
||||
if (!submenu_model)
|
||||
return;
|
||||
|
||||
// If the submenu is already built, then return right away. This means we
|
||||
// recently showed this submenu, and have not yet processed the fact that it
|
||||
// was hidden before being shown again.
|
||||
if (g_object_get_data(G_OBJECT(submenu), "submenu-built"))
|
||||
return;
|
||||
g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1));
|
||||
|
||||
submenu_model->MenuWillShow();
|
||||
|
||||
// Actually build the submenu and attach it to the parent menu item.
|
||||
BuildSubmenuFromModel(submenu_model, submenu);
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu);
|
||||
|
||||
// Update all the menu item info in the newly-generated menu.
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this);
|
||||
}
|
||||
|
||||
void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) {
|
||||
if (is_menubar_)
|
||||
return;
|
||||
|
||||
// Increase the reference count of the old submenu, and schedule it to be
|
||||
// deleted later. We get this hide notification before we've processed menu
|
||||
// activations, so if we were to delete the submenu now, we might lose the
|
||||
// activation. This also lets us reuse the menu if it is shown again before
|
||||
// it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements
|
||||
// the reference count again. Note that the delay is just an optimization; we
|
||||
// could use PostTask() and this would still work correctly.
|
||||
g_object_ref(G_OBJECT(submenu));
|
||||
base::MessageLoop::current()->PostDelayedTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu),
|
||||
base::TimeDelta::FromSeconds(2));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Remove all descendant submenu-model data pointers.
|
||||
void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) {
|
||||
if (!GTK_IS_MENU_ITEM(menu_item))
|
||||
return;
|
||||
g_object_steal_data(G_OBJECT(menu_item), "submenu-model");
|
||||
GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item));
|
||||
if (submenu)
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) {
|
||||
if (!gtk_widget_get_visible(submenu)) {
|
||||
// Remove all the children of this menu, clearing out their submenu-model
|
||||
// pointers in case they have pending calls to OnSubMenuHiddenCallback().
|
||||
// (Normally that won't happen: we'd have hidden them first, and so they'd
|
||||
// have already been deleted. But in some cases [e.g. on Ubuntu 12.04],
|
||||
// GTK menu operations may be hooked to allow external applications to
|
||||
// mirror the menu structure, and the hooks may show and hide menus in
|
||||
// order to trigger exactly the kind of dynamic menu building we're doing.
|
||||
// The result is that we see show and hide events in strange orders.)
|
||||
GList* children = gtk_container_get_children(GTK_CONTAINER(submenu));
|
||||
for (GList* child = children; child; child = g_list_next(child)) {
|
||||
RemoveSubMenuModels(GTK_WIDGET(child->data), NULL);
|
||||
gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data));
|
||||
}
|
||||
g_list_free(children);
|
||||
|
||||
// Clear out the bit that says the menu is built.
|
||||
// We'll rebuild it next time it is shown.
|
||||
g_object_steal_data(G_OBJECT(submenu), "submenu-built");
|
||||
|
||||
// Notify the submenu model that the menu has been hidden. This may cause
|
||||
// it to delete descendant submenu models, which is why we cleared those
|
||||
// pointers out above.
|
||||
GtkWidget* menu_item = static_cast<GtkWidget*>(
|
||||
g_object_get_data(G_OBJECT(submenu), "menu-item"));
|
||||
// TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110.
|
||||
CHECK(menu_item);
|
||||
ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>(
|
||||
g_object_get_data(G_OBJECT(menu_item), "submenu-model"));
|
||||
if (submenu_model)
|
||||
submenu_model->MenuClosed();
|
||||
}
|
||||
|
||||
// Remove the reference we grabbed in OnSubMenuHidden() above.
|
||||
g_object_unref(G_OBJECT(submenu));
|
||||
}
|
||||
|
||||
// static
|
||||
void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) {
|
||||
ui::ButtonMenuItemModel* model =
|
||||
reinterpret_cast<ui::ButtonMenuItemModel*>(
|
||||
g_object_get_data(G_OBJECT(button), "button-model"));
|
||||
int index = GPOINTER_TO_INT(g_object_get_data(
|
||||
G_OBJECT(button), "button-model-id"));
|
||||
|
||||
if (model->IsItemDynamicAt(index)) {
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(index)));
|
||||
gtk_button_set_label(GTK_BUTTON(button), label.c_str());
|
||||
}
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(button), model->IsEnabledAt(index));
|
||||
}
|
||||
|
||||
// static
|
||||
void MenuGtk::SetMenuItemInfo(GtkWidget* widget, gpointer userdata) {
|
||||
if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
|
||||
// We need to explicitly handle this case because otherwise we'll ask the
|
||||
// menu delegate about something with an invalid id.
|
||||
return;
|
||||
}
|
||||
|
||||
int id;
|
||||
if (!GetMenuItemID(widget, &id))
|
||||
return;
|
||||
|
||||
ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget));
|
||||
if (!model) {
|
||||
// If we're not providing the sub menu, then there's no model. For
|
||||
// example, the IME submenu doesn't have a model.
|
||||
return;
|
||||
}
|
||||
|
||||
if (GTK_IS_CHECK_MENU_ITEM(widget)) {
|
||||
GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget);
|
||||
|
||||
// gtk_check_menu_item_set_active() will send the activate signal. Touching
|
||||
// the underlying "active" property will also call the "activate" handler
|
||||
// for this menu item. So we prevent the "activate" handler from
|
||||
// being called while we set the checkbox.
|
||||
// Why not use one of the glib signal-blocking functions? Because when we
|
||||
// toggle a radio button, it will deactivate one of the other radio buttons,
|
||||
// which we don't have a pointer to.
|
||||
// Wny not make this a member variable? Because "menu" is a pointer to the
|
||||
// root of the MenuGtk and we want to disable *all* MenuGtks, including
|
||||
// submenus.
|
||||
block_activation_ = true;
|
||||
gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id));
|
||||
block_activation_ = false;
|
||||
}
|
||||
|
||||
if (GTK_IS_CUSTOM_MENU_ITEM(widget)) {
|
||||
// Iterate across all the buttons to update their visible properties.
|
||||
gtk_custom_menu_item_foreach_button(GTK_CUSTOM_MENU_ITEM(widget),
|
||||
SetButtonItemInfo,
|
||||
userdata);
|
||||
}
|
||||
|
||||
if (GTK_IS_MENU_ITEM(widget)) {
|
||||
gtk_widget_set_sensitive(widget, model->IsEnabledAt(id));
|
||||
|
||||
if (model->IsVisibleAt(id)) {
|
||||
// Update the menu item label if it is dynamic.
|
||||
if (model->IsItemDynamicAt(id)) {
|
||||
std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
|
||||
base::UTF16ToUTF8(model->GetLabelAt(id)));
|
||||
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str());
|
||||
if (GTK_IS_IMAGE_MENU_ITEM(widget)) {
|
||||
gfx::Image icon;
|
||||
if (model->GetIconAt(id, &icon)) {
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget),
|
||||
gtk_image_new_from_pixbuf(
|
||||
icon.ToGdkPixbuf()));
|
||||
} else {
|
||||
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_show(widget);
|
||||
} else {
|
||||
gtk_widget_hide(widget);
|
||||
}
|
||||
|
||||
GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget));
|
||||
if (submenu) {
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo,
|
||||
userdata);
|
||||
}
|
||||
}
|
||||
}
|
224
atom/browser/ui/gtk/menu_gtk.h
Normal file
224
atom/browser/ui/gtk/menu_gtk.h
Normal file
|
@ -0,0 +1,224 @@
|
|||
// 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_GTK_MENU_GTK_H_
|
||||
#define ATOM_BROWSER_UI_GTK_MENU_GTK_H_
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "ui/base/gtk/gtk_signal.h"
|
||||
#include "ui/base/gtk/gtk_signal_registrar.h"
|
||||
#include "ui/gfx/point.h"
|
||||
|
||||
namespace gfx {
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class ButtonMenuItemModel;
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
class MenuGtk {
|
||||
public:
|
||||
// Delegate class that lets another class control the status of the menu.
|
||||
class Delegate {
|
||||
public:
|
||||
virtual ~Delegate() {}
|
||||
|
||||
// Called before a command is executed. This exists for the case where a
|
||||
// model is handling the actual execution of commands, but the delegate
|
||||
// still needs to know that some command got executed. This is called before
|
||||
// and not after the command is executed because its execution may delete
|
||||
// the menu and/or the delegate.
|
||||
virtual void CommandWillBeExecuted() {}
|
||||
|
||||
// Called when the menu stops showing. This will be called before
|
||||
// ExecuteCommand if the user clicks an item, but will also be called when
|
||||
// the user clicks away from the menu.
|
||||
virtual void StoppedShowing() {}
|
||||
|
||||
// Return true if we should override the "gtk-menu-images" system setting
|
||||
// when showing image menu items for this menu.
|
||||
virtual bool AlwaysShowIconForCmd(int command_id) const;
|
||||
|
||||
// Returns a tinted image used in button in a menu.
|
||||
virtual GtkIconSet* GetIconSetForId(int idr);
|
||||
|
||||
// Returns an icon for the menu item, if available.
|
||||
virtual GtkWidget* GetImageForCommandId(int command_id) const;
|
||||
|
||||
static GtkWidget* GetDefaultImageForLabel(const std::string& label);
|
||||
};
|
||||
|
||||
MenuGtk(MenuGtk::Delegate* delegate,
|
||||
ui::MenuModel* model,
|
||||
bool is_menubar = false);
|
||||
virtual ~MenuGtk();
|
||||
|
||||
// Initialize GTK signal handlers.
|
||||
void ConnectSignalHandlers();
|
||||
|
||||
// These methods are used to build the menu dynamically. The return value
|
||||
// is the new menu item.
|
||||
GtkWidget* AppendMenuItemWithLabel(int command_id, const std::string& label);
|
||||
GtkWidget* AppendMenuItemWithIcon(int command_id, const std::string& label,
|
||||
const gfx::Image& icon);
|
||||
GtkWidget* AppendCheckMenuItemWithLabel(int command_id,
|
||||
const std::string& label);
|
||||
GtkWidget* AppendSeparator();
|
||||
GtkWidget* InsertSeparator(int position);
|
||||
GtkWidget* AppendMenuItem(int command_id, GtkWidget* menu_item);
|
||||
GtkWidget* InsertMenuItem(int command_id, GtkWidget* menu_item, int position);
|
||||
GtkWidget* AppendMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
bool connect_to_activate);
|
||||
GtkWidget* InsertMenuItemToMenu(int index,
|
||||
ui::MenuModel* model,
|
||||
GtkWidget* menu_item,
|
||||
GtkWidget* menu,
|
||||
int position,
|
||||
bool connect_to_activate);
|
||||
|
||||
// Displays the menu near a widget, as if the widget were a menu bar.
|
||||
// Example: the wrench menu button.
|
||||
// |button| is the mouse button that brought up the menu.
|
||||
// |event_time| is the time from the GdkEvent.
|
||||
void PopupForWidget(GtkWidget* widget, int button, guint32 event_time);
|
||||
|
||||
// Displays the menu as a context menu, i.e. at the cursor location.
|
||||
// It is implicit that it was brought up using the right mouse button.
|
||||
// |point| is the point where to put the menu.
|
||||
// |event_time| is the time of the event that triggered the menu's display.
|
||||
void PopupAsContext(const gfx::Point& point, guint32 event_time);
|
||||
|
||||
// Displays the menu as a context menu for the passed status icon.
|
||||
void PopupAsContextForStatusIcon(guint32 event_time, guint32 button,
|
||||
GtkStatusIcon* icon);
|
||||
|
||||
// Displays the menu following a keyboard event (such as selecting |widget|
|
||||
// and pressing "enter").
|
||||
void PopupAsFromKeyEvent(GtkWidget* widget);
|
||||
|
||||
// Closes the menu.
|
||||
void Cancel();
|
||||
|
||||
// Repositions the menu to be right under the button. Alignment is set as
|
||||
// object data on |void_widget| with the tag "left_align". If "left_align"
|
||||
// is true, it aligns the left side of the menu with the left side of the
|
||||
// button. Otherwise it aligns the right side of the menu with the right side
|
||||
// of the button. Public since some menus have odd requirements that don't
|
||||
// belong in a public class.
|
||||
static void WidgetMenuPositionFunc(GtkMenu* menu,
|
||||
int* x,
|
||||
int* y,
|
||||
gboolean* push_in,
|
||||
void* void_widget);
|
||||
|
||||
// Positions the menu to appear at the gfx::Point represented by |userdata|.
|
||||
static void PointMenuPositionFunc(GtkMenu* menu,
|
||||
int* x,
|
||||
int* y,
|
||||
gboolean* push_in,
|
||||
gpointer userdata);
|
||||
|
||||
GtkWidget* widget() const { return menu_; }
|
||||
ui::MenuModel* model() const { return model_;}
|
||||
|
||||
// Updates all the enabled/checked states and the dynamic labels.
|
||||
void UpdateMenu();
|
||||
|
||||
private:
|
||||
// Builds a GtkImageMenuItem.
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label,
|
||||
const gfx::Image& icon);
|
||||
|
||||
GtkWidget* BuildMenuItemWithImage(const std::string& label,
|
||||
GtkWidget* image);
|
||||
|
||||
GtkWidget* BuildMenuItemWithLabel(const std::string& label,
|
||||
int command_id);
|
||||
|
||||
// A function that creates a GtkMenu from |model_|.
|
||||
void BuildMenuFromModel();
|
||||
// Implementation of the above; called recursively.
|
||||
void BuildSubmenuFromModel(ui::MenuModel* model, GtkWidget* menu);
|
||||
// Builds a menu item with buttons in it from the data in the model.
|
||||
GtkWidget* BuildButtonMenuItem(ui::ButtonMenuItemModel* model,
|
||||
GtkWidget* menu);
|
||||
|
||||
void ExecuteCommand(ui::MenuModel* model, int id);
|
||||
|
||||
// Callback for when a menu item is clicked.
|
||||
CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuItemActivated);
|
||||
|
||||
// Called when one of the buttons is pressed.
|
||||
CHROMEGTK_CALLBACK_1(MenuGtk, void, OnMenuButtonPressed, int);
|
||||
|
||||
// Called to maybe activate a button if that button isn't supposed to dismiss
|
||||
// the menu.
|
||||
CHROMEGTK_CALLBACK_1(MenuGtk, gboolean, OnMenuTryButtonPressed, int);
|
||||
|
||||
// Updates all the menu items' state.
|
||||
CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuShow);
|
||||
|
||||
// Sets the activating widget back to a normal appearance.
|
||||
CHROMEGTK_CALLBACK_0(MenuGtk, void, OnMenuHidden);
|
||||
|
||||
// Focus out event handler for the menu.
|
||||
CHROMEGTK_CALLBACK_1(MenuGtk, gboolean, OnMenuFocusOut, GdkEventFocus*);
|
||||
|
||||
// Handles building dynamic submenus on demand when they are shown.
|
||||
CHROMEGTK_CALLBACK_0(MenuGtk, void, OnSubMenuShow);
|
||||
|
||||
// Handles trearing down dynamic submenus when they have been closed.
|
||||
CHROMEGTK_CALLBACK_0(MenuGtk, void, OnSubMenuHidden);
|
||||
|
||||
// Scheduled by OnSubMenuHidden() to avoid deleting submenus when hidden
|
||||
// before pending activations within them are delivered.
|
||||
static void OnSubMenuHiddenCallback(GtkWidget* submenu);
|
||||
|
||||
// Sets the enable/disabled state and dynamic labels on our menu items.
|
||||
static void SetButtonItemInfo(GtkWidget* button, gpointer userdata);
|
||||
|
||||
// Sets the check mark, enabled/disabled state and dynamic labels on our menu
|
||||
// items.
|
||||
static void SetMenuItemInfo(GtkWidget* widget, void* raw_menu);
|
||||
|
||||
// Queries this object about the menu state.
|
||||
MenuGtk::Delegate* delegate_;
|
||||
|
||||
// If non-NULL, the MenuModel that we use to populate and control the GTK
|
||||
// menu (overriding the delegate as a controller).
|
||||
ui::MenuModel* model_;
|
||||
|
||||
// Whether this is a menu bar.
|
||||
bool is_menubar_;
|
||||
|
||||
// For some menu items, we want to show the accelerator, but not actually
|
||||
// explicitly handle it. To this end we connect those menu items' accelerators
|
||||
// to this group, but don't attach this group to any top level window.
|
||||
GtkAccelGroup* dummy_accel_group_;
|
||||
|
||||
// gtk_menu_popup() does not appear to take ownership of popup menus, so
|
||||
// MenuGtk explicitly manages the lifetime of the menu.
|
||||
GtkWidget* menu_;
|
||||
|
||||
// True when we should ignore "activate" signals. Used to prevent
|
||||
// menu items from getting activated when we are setting up the
|
||||
// menu.
|
||||
static bool block_activation_;
|
||||
|
||||
ui::GtkSignalRegistrar signal_;
|
||||
|
||||
base::WeakPtrFactory<MenuGtk> weak_factory_;
|
||||
};
|
||||
|
||||
#endif // ATOM_BROWSER_UI_GTK_MENU_GTK_H_
|
42
atom/browser/ui/message_box.h
Normal file
42
atom/browser/ui/message_box.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BROWSER_UI_MESSAGE_BOX_H_
|
||||
#define BROWSER_UI_MESSAGE_BOX_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeWindow;
|
||||
|
||||
enum MessageBoxType {
|
||||
MESSAGE_BOX_TYPE_NONE = 0,
|
||||
MESSAGE_BOX_TYPE_INFORMATION,
|
||||
MESSAGE_BOX_TYPE_WARNING
|
||||
};
|
||||
|
||||
typedef base::Callback<void(int code)> MessageBoxCallback;
|
||||
|
||||
int ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail);
|
||||
|
||||
void ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const MessageBoxCallback& callback);
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // BROWSER_UI_MESSAGE_BOX_H_
|
132
atom/browser/ui/message_box_gtk.cc
Normal file
132
atom/browser/ui/message_box_gtk.cc
Normal file
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) 2014 GitHub, Inc. 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/message_box.h"
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/gtk/gtk_util.h"
|
||||
#include "ui/base/gtk/gtk_signal.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
class MessageBox {
|
||||
public:
|
||||
MessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail)
|
||||
: cancel_id_(0) {
|
||||
GtkWindow* window = parent_window ? parent_window->GetNativeWindow() : NULL;
|
||||
dialog_ = gtk_dialog_new_with_buttons(
|
||||
title.c_str(),
|
||||
window,
|
||||
static_cast<GtkDialogFlags>(GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
|
||||
NULL);
|
||||
|
||||
for (size_t i = 0; i < buttons.size(); ++i)
|
||||
gtk_dialog_add_button(GTK_DIALOG(dialog_),
|
||||
TranslateToStock(i, buttons[i]),
|
||||
i);
|
||||
|
||||
GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
|
||||
GtkWidget* message_label = gtk_util::CreateBoldLabel(message);
|
||||
gtk_util::LeftAlignMisc(message_label);
|
||||
gtk_box_pack_start(GTK_BOX(content_area), message_label, FALSE, FALSE, 0);
|
||||
GtkWidget* detail_label = gtk_label_new(detail.c_str());
|
||||
gtk_util::LeftAlignMisc(detail_label);
|
||||
gtk_box_pack_start(GTK_BOX(content_area), detail_label, FALSE, FALSE, 0);
|
||||
|
||||
gtk_window_set_resizable(GTK_WINDOW(dialog_), FALSE);
|
||||
}
|
||||
|
||||
~MessageBox() {
|
||||
gtk_widget_destroy(dialog_);
|
||||
}
|
||||
|
||||
const char* TranslateToStock(int id, const std::string& text) {
|
||||
if (LowerCaseEqualsASCII(text, "cancel")) {
|
||||
cancel_id_ = id;
|
||||
return GTK_STOCK_CANCEL;
|
||||
} else if (LowerCaseEqualsASCII(text, "no")) {
|
||||
cancel_id_ = id;
|
||||
return GTK_STOCK_NO;
|
||||
} else if (LowerCaseEqualsASCII(text, "ok")) {
|
||||
return GTK_STOCK_OK;
|
||||
} else if (LowerCaseEqualsASCII(text, "yes")) {
|
||||
return GTK_STOCK_YES;
|
||||
} else {
|
||||
return text.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
void RunAsynchronous(const MessageBoxCallback& callback) {
|
||||
callback_ = callback;
|
||||
g_signal_connect(dialog_, "delete-event",
|
||||
G_CALLBACK(gtk_widget_hide_on_delete), NULL);
|
||||
g_signal_connect(dialog_, "response",
|
||||
G_CALLBACK(OnResponseDialogThunk), this);
|
||||
gtk_widget_show_all(dialog_);
|
||||
}
|
||||
|
||||
CHROMEGTK_CALLBACK_1(MessageBox, void, OnResponseDialog, int);
|
||||
|
||||
GtkWidget* dialog() const { return dialog_; }
|
||||
int cancel_id() const { return cancel_id_; }
|
||||
|
||||
private:
|
||||
GtkWidget* dialog_;
|
||||
MessageBoxCallback callback_;
|
||||
|
||||
// The id to return when the dialog is closed without pressing buttons.
|
||||
int cancel_id_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MessageBox);
|
||||
};
|
||||
|
||||
void MessageBox::OnResponseDialog(GtkWidget* widget, int response) {
|
||||
gtk_widget_hide_all(dialog_);
|
||||
|
||||
if (response < 0)
|
||||
callback_.Run(cancel_id_);
|
||||
else
|
||||
callback_.Run(response);
|
||||
delete this;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail) {
|
||||
MessageBox message_box(parent_window, type, buttons, title, message, detail);
|
||||
gtk_widget_show_all(message_box.dialog());
|
||||
int response = gtk_dialog_run(GTK_DIALOG(message_box.dialog()));
|
||||
if (response < 0)
|
||||
return message_box.cancel_id();
|
||||
else
|
||||
return response;
|
||||
}
|
||||
|
||||
void ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const MessageBoxCallback& callback) {
|
||||
MessageBox* message_box = new MessageBox(
|
||||
parent_window, type, buttons, title, message, detail);
|
||||
message_box->RunAsynchronous(callback);
|
||||
}
|
||||
|
||||
} // namespace atom
|
141
atom/browser/ui/message_box_mac.mm
Normal file
141
atom/browser/ui/message_box_mac.mm
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/message_box.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
|
||||
@interface ModalDelegate : NSObject {
|
||||
@private
|
||||
atom::MessageBoxCallback callback_;
|
||||
NSAlert* alert_;
|
||||
bool callEndModal_;
|
||||
}
|
||||
- (id)initWithCallback:(const atom::MessageBoxCallback&)callback
|
||||
andAlert:(NSAlert*)alert
|
||||
callEndModal:(bool)flag;
|
||||
@end
|
||||
|
||||
@implementation ModalDelegate
|
||||
|
||||
- (id)initWithCallback:(const atom::MessageBoxCallback&)callback
|
||||
andAlert:(NSAlert*)alert
|
||||
callEndModal:(bool)flag {
|
||||
if ((self = [super init])) {
|
||||
callback_ = callback;
|
||||
alert_ = alert;
|
||||
callEndModal_ = flag;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)alertDidEnd:(NSAlert*)alert
|
||||
returnCode:(NSInteger)returnCode
|
||||
contextInfo:(void*)contextInfo {
|
||||
callback_.Run(returnCode);
|
||||
[alert_ release];
|
||||
[self release];
|
||||
|
||||
if (callEndModal_)
|
||||
[NSApp stopModal];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
NSAlert* CreateNSAlert(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail) {
|
||||
// Ignore the title; it's the window title on other platforms and ignorable.
|
||||
NSAlert* alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:base::SysUTF8ToNSString(message)];
|
||||
[alert setInformativeText:base::SysUTF8ToNSString(detail)];
|
||||
|
||||
switch (type) {
|
||||
case MESSAGE_BOX_TYPE_INFORMATION:
|
||||
[alert setAlertStyle:NSInformationalAlertStyle];
|
||||
break;
|
||||
case MESSAGE_BOX_TYPE_WARNING:
|
||||
[alert setAlertStyle:NSWarningAlertStyle];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||
NSString* title = base::SysUTF8ToNSString(buttons[i]);
|
||||
NSButton* button = [alert addButtonWithTitle:title];
|
||||
[button setTag:i];
|
||||
}
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
void SetReturnCode(int* ret_code, int result) {
|
||||
*ret_code = result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail) {
|
||||
NSAlert* alert = CreateNSAlert(
|
||||
parent_window, type, buttons, title, message, detail);
|
||||
|
||||
// Use runModal for synchronous alert without parent, since we don't have a
|
||||
// window to wait for.
|
||||
if (!parent_window)
|
||||
return [[alert autorelease] runModal];
|
||||
|
||||
int ret_code = -1;
|
||||
ModalDelegate* delegate = [[ModalDelegate alloc]
|
||||
initWithCallback:base::Bind(&SetReturnCode, &ret_code)
|
||||
andAlert:alert
|
||||
callEndModal:true];
|
||||
|
||||
NSWindow* window = parent_window->GetNativeWindow();
|
||||
[alert beginSheetModalForWindow:window
|
||||
modalDelegate:delegate
|
||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
|
||||
contextInfo:nil];
|
||||
|
||||
[NSApp runModalForWindow:window];
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
void ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const MessageBoxCallback& callback) {
|
||||
NSAlert* alert = CreateNSAlert(
|
||||
parent_window, type, buttons, title, message, detail);
|
||||
ModalDelegate* delegate = [[ModalDelegate alloc] initWithCallback:callback
|
||||
andAlert:alert
|
||||
callEndModal:false];
|
||||
|
||||
NSWindow* window = parent_window ? parent_window->GetNativeWindow() : nil;
|
||||
[alert beginSheetModalForWindow:window
|
||||
modalDelegate:delegate
|
||||
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
|
||||
contextInfo:nil];
|
||||
}
|
||||
|
||||
} // namespace atom
|
297
atom/browser/ui/message_box_win.cc
Normal file
297
atom/browser/ui/message_box_win.cc
Normal file
|
@ -0,0 +1,297 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. 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/message_box.h"
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "skia/ext/skia_utils_win.h"
|
||||
#include "ui/views/controls/button/label_button.h"
|
||||
#include "ui/views/controls/message_box_view.h"
|
||||
#include "ui/views/layout/grid_layout.h"
|
||||
#include "ui/views/layout/layout_constants.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// The group used by the buttons. This name is chosen voluntarily big not to
|
||||
// conflict with other groups that could be in the dialog content.
|
||||
const int kButtonGroup = 1127;
|
||||
|
||||
class MessageDialog : public base::MessageLoop::Dispatcher,
|
||||
public views::WidgetDelegate,
|
||||
public views::View,
|
||||
public views::ButtonListener {
|
||||
public:
|
||||
MessageDialog(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail);
|
||||
virtual ~MessageDialog();
|
||||
|
||||
void Show();
|
||||
|
||||
int GetResult() const;
|
||||
|
||||
void set_callback(const MessageBoxCallback& callback) {
|
||||
delete_on_close_ = true;
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
// Overridden from MessageLoop::Dispatcher:
|
||||
virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE;
|
||||
|
||||
// Overridden from views::WidgetDelegate:
|
||||
virtual string16 GetWindowTitle() const;
|
||||
virtual void WindowClosing() OVERRIDE;
|
||||
virtual views::Widget* GetWidget() OVERRIDE;
|
||||
virtual const views::Widget* GetWidget() const OVERRIDE;
|
||||
virtual views::View* GetContentsView() OVERRIDE;
|
||||
virtual views::View* GetInitiallyFocusedView() OVERRIDE;
|
||||
virtual ui::ModalType GetModalType() const OVERRIDE;
|
||||
|
||||
// Overridden from views::View:
|
||||
virtual gfx::Size GetPreferredSize() OVERRIDE;
|
||||
virtual void Layout() OVERRIDE;
|
||||
virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE;
|
||||
|
||||
// Overridden from views::ButtonListener:
|
||||
virtual void ButtonPressed(views::Button* sender,
|
||||
const ui::Event& event) OVERRIDE;
|
||||
|
||||
bool should_close_;
|
||||
bool delete_on_close_;
|
||||
int result_;
|
||||
string16 title_;
|
||||
views::Widget* widget_;
|
||||
views::MessageBoxView* message_box_view_;
|
||||
scoped_ptr<NativeWindow::DialogScope> dialog_scope_;
|
||||
std::vector<views::LabelButton*> buttons_;
|
||||
MessageBoxCallback callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MessageDialog);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// MessageDialog, public:
|
||||
|
||||
MessageDialog::MessageDialog(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail)
|
||||
: should_close_(false),
|
||||
delete_on_close_(false),
|
||||
result_(-1),
|
||||
title_(UTF8ToUTF16(title)),
|
||||
widget_(NULL),
|
||||
message_box_view_(NULL),
|
||||
dialog_scope_(new NativeWindow::DialogScope(parent_window)) {
|
||||
DCHECK_GT(buttons.size(), 0u);
|
||||
set_owned_by_client();
|
||||
|
||||
views::MessageBoxView::InitParams params(UTF8ToUTF16(title));
|
||||
params.message = UTF8ToUTF16(message + "\n" + detail);
|
||||
message_box_view_ = new views::MessageBoxView(params);
|
||||
AddChildView(message_box_view_);
|
||||
|
||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||
views::LabelButton* button = new views::LabelButton(
|
||||
this, UTF8ToUTF16(buttons[i]));
|
||||
button->set_tag(i);
|
||||
button->set_min_size(gfx::Size(60, 20));
|
||||
button->SetStyle(views::Button::STYLE_NATIVE_TEXTBUTTON);
|
||||
button->SetGroup(kButtonGroup);
|
||||
|
||||
buttons_.push_back(button);
|
||||
AddChildView(button);
|
||||
}
|
||||
|
||||
// First button is always default button.
|
||||
buttons_[0]->SetIsDefault(true);
|
||||
buttons_[0]->AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
|
||||
|
||||
views::Widget::InitParams widget_params;
|
||||
widget_params.delegate = this;
|
||||
widget_params.top_level = true;
|
||||
if (parent_window)
|
||||
widget_params.parent = parent_window->GetNativeWindow();
|
||||
widget_ = new views::Widget;
|
||||
widget_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_NATIVE);
|
||||
widget_->Init(widget_params);
|
||||
|
||||
// Bind to ESC.
|
||||
AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
|
||||
|
||||
set_background(views::Background::CreateSolidBackground(
|
||||
skia::COLORREFToSkColor(GetSysColor(COLOR_WINDOW))));
|
||||
}
|
||||
|
||||
MessageDialog::~MessageDialog() {
|
||||
}
|
||||
|
||||
void MessageDialog::Show() {
|
||||
widget_->Show();
|
||||
}
|
||||
|
||||
int MessageDialog::GetResult() const {
|
||||
// When the dialog is closed without choosing anything, we think the user
|
||||
// chose 'Cancel', otherwise we think the default behavior is chosen.
|
||||
if (result_ == -1) {
|
||||
for (size_t i = 0; i < buttons_.size(); ++i)
|
||||
if (LowerCaseEqualsASCII(buttons_[i]->GetText(), "cancel")) {
|
||||
return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
return result_;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// MessageDialog, private:
|
||||
|
||||
bool MessageDialog::Dispatch(const base::NativeEvent& event) {
|
||||
TranslateMessage(&event);
|
||||
DispatchMessage(&event);
|
||||
return !should_close_;
|
||||
}
|
||||
|
||||
string16 MessageDialog::GetWindowTitle() const {
|
||||
return title_;
|
||||
}
|
||||
|
||||
void MessageDialog::WindowClosing() {
|
||||
should_close_ = true;
|
||||
dialog_scope_.reset();
|
||||
|
||||
if (delete_on_close_) {
|
||||
callback_.Run(GetResult());
|
||||
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
|
||||
}
|
||||
}
|
||||
|
||||
views::Widget* MessageDialog::GetWidget() {
|
||||
return widget_;
|
||||
}
|
||||
|
||||
const views::Widget* MessageDialog::GetWidget() const {
|
||||
return widget_;
|
||||
}
|
||||
|
||||
views::View* MessageDialog::GetContentsView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
views::View* MessageDialog::GetInitiallyFocusedView() {
|
||||
if (buttons_.size() > 0)
|
||||
return buttons_[0];
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
ui::ModalType MessageDialog::GetModalType() const {
|
||||
return ui::MODAL_TYPE_WINDOW;
|
||||
}
|
||||
|
||||
gfx::Size MessageDialog::GetPreferredSize() {
|
||||
gfx::Size size(0, buttons_[0]->GetPreferredSize().height());
|
||||
for (size_t i = 0; i < buttons_.size(); ++i)
|
||||
size.Enlarge(buttons_[i]->GetPreferredSize().width(), 0);
|
||||
|
||||
// Button spaces.
|
||||
size.Enlarge(views::kRelatedButtonHSpacing * (buttons_.size() - 1),
|
||||
views::kRelatedControlVerticalSpacing);
|
||||
|
||||
// The message box view.
|
||||
gfx::Size contents_size = message_box_view_->GetPreferredSize();
|
||||
size.Enlarge(0, contents_size.height());
|
||||
if (contents_size.width() > size.width())
|
||||
size.set_width(contents_size.width());
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void MessageDialog::Layout() {
|
||||
gfx::Rect bounds = GetContentsBounds();
|
||||
|
||||
// Layout the row containing the buttons.
|
||||
int x = bounds.width();
|
||||
int height = buttons_[0]->GetPreferredSize().height() +
|
||||
views::kRelatedControlVerticalSpacing;
|
||||
|
||||
// NB: We iterate through the buttons backwards here because
|
||||
// Mac and Windows buttons are laid out in opposite order.
|
||||
for (int i = buttons_.size() - 1; i >= 0; --i) {
|
||||
gfx::Size size = buttons_[i]->GetPreferredSize();
|
||||
x -= size.width() + views::kRelatedButtonHSpacing;
|
||||
|
||||
buttons_[i]->SetBounds(x, bounds.height() - height,
|
||||
size.width(), size.height());
|
||||
}
|
||||
|
||||
// Layout the message box view.
|
||||
message_box_view_->SetBounds(bounds.x(), bounds.y(), bounds.width(),
|
||||
bounds.height() - height);
|
||||
}
|
||||
|
||||
bool MessageDialog::AcceleratorPressed(const ui::Accelerator& accelerator) {
|
||||
DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
|
||||
widget_->Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageDialog::ButtonPressed(views::Button* sender,
|
||||
const ui::Event& event) {
|
||||
result_ = sender->tag();
|
||||
widget_->Close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail) {
|
||||
MessageDialog dialog(parent_window, type, buttons, title, message, detail);
|
||||
dialog.Show();
|
||||
{
|
||||
base::MessageLoop::ScopedNestableTaskAllower allow(
|
||||
base::MessageLoopForUI::current());
|
||||
base::RunLoop run_loop(&dialog);
|
||||
run_loop.Run();
|
||||
}
|
||||
|
||||
return dialog.GetResult();
|
||||
}
|
||||
|
||||
void ShowMessageBox(NativeWindow* parent_window,
|
||||
MessageBoxType type,
|
||||
const std::vector<std::string>& buttons,
|
||||
const std::string& title,
|
||||
const std::string& message,
|
||||
const std::string& detail,
|
||||
const MessageBoxCallback& callback) {
|
||||
// The dialog would be deleted when the dialog is closed.
|
||||
MessageDialog* dialog = new MessageDialog(
|
||||
parent_window, type, buttons, title, message, detail);
|
||||
dialog->set_callback(callback);
|
||||
dialog->Show();
|
||||
}
|
||||
|
||||
} // namespace atom
|
65
atom/browser/ui/win/menu_2.cc
Normal file
65
atom/browser/ui/win/menu_2.cc
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// 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/win/menu_2.h"
|
||||
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/views/controls/menu/menu_listener.h"
|
||||
|
||||
// Really bad hack here, renaming all class names would be too much work.
|
||||
using namespace views; // NOLINT
|
||||
|
||||
namespace atom {
|
||||
|
||||
Menu2::Menu2(ui::MenuModel* model, bool as_window_menu)
|
||||
: model_(model),
|
||||
wrapper_(new NativeMenuWin(model, NULL)) {
|
||||
wrapper_->set_create_as_window_menu(as_window_menu);
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
Menu2::~Menu2() {}
|
||||
|
||||
HMENU Menu2::GetNativeMenu() const {
|
||||
return wrapper_->GetNativeMenu();
|
||||
}
|
||||
|
||||
void Menu2::RunMenuAt(const gfx::Point& point, Alignment alignment) {
|
||||
wrapper_->RunMenuAt(point, alignment);
|
||||
}
|
||||
|
||||
void Menu2::RunContextMenuAt(const gfx::Point& point) {
|
||||
RunMenuAt(point, ALIGN_TOPLEFT);
|
||||
}
|
||||
|
||||
void Menu2::CancelMenu() {
|
||||
wrapper_->CancelMenu();
|
||||
}
|
||||
|
||||
void Menu2::Rebuild() {
|
||||
wrapper_->Rebuild(NULL);
|
||||
}
|
||||
|
||||
void Menu2::UpdateStates() {
|
||||
wrapper_->UpdateStates();
|
||||
}
|
||||
|
||||
NativeMenuWin::MenuAction Menu2::GetMenuAction() const {
|
||||
return wrapper_->GetMenuAction();
|
||||
}
|
||||
|
||||
void Menu2::AddMenuListener(MenuListener* listener) {
|
||||
wrapper_->AddMenuListener(listener);
|
||||
}
|
||||
|
||||
void Menu2::RemoveMenuListener(MenuListener* listener) {
|
||||
wrapper_->RemoveMenuListener(listener);
|
||||
}
|
||||
|
||||
void Menu2::SetMinimumWidth(int width) {
|
||||
wrapper_->SetMinimumWidth(width);
|
||||
}
|
||||
|
||||
} // namespace atom
|
96
atom/browser/ui/win/menu_2.h
Normal file
96
atom/browser/ui/win/menu_2.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// 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 file.
|
||||
|
||||
#ifndef BROWSER_UI_WIN_MENU_2_H_
|
||||
#define BROWSER_UI_WIN_MENU_2_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "atom/browser/ui/win/native_menu_win.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
namespace gfx {
|
||||
class Point;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
// A menu. Populated from a model, and relies on a delegate to execute commands.
|
||||
//
|
||||
// WARNING: do NOT create and use Menu2 on the stack. Menu2 notifies the model
|
||||
// of selection AFTER a delay. This means that if use a Menu2 on the stack
|
||||
// ActivatedAt is never invoked.
|
||||
class Menu2 {
|
||||
public:
|
||||
// How the menu is aligned relative to the point it is shown at.
|
||||
// The alignment is reversed by menu if text direction is right to left.
|
||||
enum Alignment {
|
||||
ALIGN_TOPLEFT,
|
||||
ALIGN_TOPRIGHT
|
||||
};
|
||||
|
||||
// Creates a new menu populated with the contents of |model|.
|
||||
// WARNING: this populates the menu on construction by invoking methods on
|
||||
// the model. As such, it is typically not safe to use this as the model
|
||||
// from the constructor. EG:
|
||||
// MyClass : menu_(this) {}
|
||||
// is likely to have problems.
|
||||
explicit Menu2(ui::MenuModel* model, bool as_window_menu = false);
|
||||
virtual ~Menu2();
|
||||
|
||||
// Runs the menu at the specified point. This method blocks until done.
|
||||
// RunContextMenuAt is the same, but the alignment is the default for a
|
||||
// context menu.
|
||||
void RunMenuAt(const gfx::Point& point, Alignment alignment);
|
||||
void RunContextMenuAt(const gfx::Point& point);
|
||||
|
||||
// Cancels the active menu.
|
||||
void CancelMenu();
|
||||
|
||||
// Called when the model supplying data to this menu has changed, and the menu
|
||||
// must be rebuilt.
|
||||
void Rebuild();
|
||||
|
||||
// Called when the states of the menu items in the menu should be refreshed
|
||||
// from the model.
|
||||
void UpdateStates();
|
||||
|
||||
// For submenus.
|
||||
HMENU GetNativeMenu() const;
|
||||
|
||||
// Get the result of the last call to RunMenuAt to determine whether an
|
||||
// item was selected, the user navigated to a next or previous menu, or
|
||||
// nothing.
|
||||
NativeMenuWin::MenuAction GetMenuAction() const;
|
||||
|
||||
// Add a listener to receive a callback when the menu opens.
|
||||
void AddMenuListener(views::MenuListener* listener);
|
||||
|
||||
// Remove a menu listener.
|
||||
void RemoveMenuListener(views::MenuListener* listener);
|
||||
|
||||
// Accessors.
|
||||
ui::MenuModel* model() const { return model_; }
|
||||
NativeMenuWin* wrapper() const { return wrapper_.get(); }
|
||||
|
||||
// Sets the minimum width of the menu.
|
||||
void SetMinimumWidth(int width);
|
||||
|
||||
private:
|
||||
ui::MenuModel* model_;
|
||||
|
||||
// The object that actually implements the menu.
|
||||
scoped_ptr<NativeMenuWin> wrapper_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Menu2);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // BROWSER_UI_WIN_MENU_2_H_
|
765
atom/browser/ui/win/native_menu_win.cc
Normal file
765
atom/browser/ui/win/native_menu_win.cc
Normal file
|
@ -0,0 +1,765 @@
|
|||
// 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/win/native_menu_win.h"
|
||||
|
||||
#include <Windowsx.h>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/win/wrapped_window_proc.h"
|
||||
#include "atom/browser/ui/win/menu_2.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/events/keycodes/keyboard_codes.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/l10n/l10n_util_win.h"
|
||||
#include "ui/base/models/menu_model.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/font.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/gfx/image/image_skia.h"
|
||||
#include "ui/gfx/rect.h"
|
||||
#include "ui/gfx/win/hwnd_util.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "ui/native_theme/native_theme_win.h"
|
||||
#include "ui/views/controls/menu/menu_config.h"
|
||||
#include "ui/views/controls/menu/menu_insertion_delegate_win.h"
|
||||
#include "ui/views/controls/menu/menu_listener.h"
|
||||
|
||||
using ui::NativeTheme;
|
||||
|
||||
// Really bad hack here, renaming all class names would be too much work.
|
||||
using namespace views; // NOLINT
|
||||
|
||||
namespace atom {
|
||||
|
||||
// The width of an icon, including the pixels between the icon and
|
||||
// the item label.
|
||||
static const int kIconWidth = 23;
|
||||
// Margins between the top of the item and the label.
|
||||
static const int kItemTopMargin = 3;
|
||||
// Margins between the bottom of the item and the label.
|
||||
static const int kItemBottomMargin = 4;
|
||||
// Margins between the left of the item and the icon.
|
||||
static const int kItemLeftMargin = 4;
|
||||
// Margins between the right of the item and the label.
|
||||
static const int kItemRightMargin = 10;
|
||||
// The width for displaying the sub-menu arrow.
|
||||
static const int kArrowWidth = 10;
|
||||
|
||||
struct NativeMenuWin::ItemData {
|
||||
// The Windows API requires that whoever creates the menus must own the
|
||||
// strings used for labels, and keep them around for the lifetime of the
|
||||
// created menu. So be it.
|
||||
string16 label;
|
||||
|
||||
// Someone needs to own submenus, it may as well be us.
|
||||
scoped_ptr<Menu2> submenu;
|
||||
|
||||
// We need a pointer back to the containing menu in various circumstances.
|
||||
NativeMenuWin* native_menu_win;
|
||||
|
||||
// The index of the item within the menu's model.
|
||||
int model_index;
|
||||
};
|
||||
|
||||
// Returns the NativeMenuWin for a particular HMENU.
|
||||
static NativeMenuWin* GetNativeMenuWinFromHMENU(HMENU hmenu) {
|
||||
MENUINFO mi = {0};
|
||||
mi.cbSize = sizeof(mi);
|
||||
mi.fMask = MIM_MENUDATA | MIM_STYLE;
|
||||
GetMenuInfo(hmenu, &mi);
|
||||
return reinterpret_cast<NativeMenuWin*>(mi.dwMenuData);
|
||||
}
|
||||
|
||||
// A window that receives messages from Windows relevant to the native menu
|
||||
// structure we have constructed in NativeMenuWin.
|
||||
class NativeMenuWin::MenuHostWindow {
|
||||
public:
|
||||
explicit MenuHostWindow(NativeMenuWin* parent) : parent_(parent) {
|
||||
RegisterClass();
|
||||
hwnd_ = CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName,
|
||||
L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
|
||||
gfx::CheckWindowCreated(hwnd_);
|
||||
gfx::SetWindowUserData(hwnd_, this);
|
||||
}
|
||||
|
||||
~MenuHostWindow() {
|
||||
DestroyWindow(hwnd_);
|
||||
}
|
||||
|
||||
// Called when the user selects a specific item.
|
||||
void OnMenuCommand(int position, HMENU menu) {
|
||||
NativeMenuWin* menu_win = GetNativeMenuWinFromHMENU(menu);
|
||||
ui::MenuModel* model = menu_win->model_;
|
||||
NativeMenuWin* root_menu = menu_win;
|
||||
while (root_menu->parent_)
|
||||
root_menu = root_menu->parent_;
|
||||
|
||||
// Only notify the model if it didn't already send out notification.
|
||||
// See comment in MenuMessageHook for details.
|
||||
if (root_menu->menu_action_ == MENU_ACTION_NONE)
|
||||
model->ActivatedAt(position);
|
||||
}
|
||||
|
||||
HWND hwnd() const { return hwnd_; }
|
||||
|
||||
private:
|
||||
static const wchar_t* kWindowClassName;
|
||||
|
||||
void RegisterClass() {
|
||||
static bool registered = false;
|
||||
if (registered)
|
||||
return;
|
||||
|
||||
WNDCLASSEX window_class;
|
||||
base::win::InitializeWindowClass(
|
||||
kWindowClassName,
|
||||
&base::win::WrappedWindowProc<MenuHostWindowProc>,
|
||||
CS_DBLCLKS,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
reinterpret_cast<HBRUSH>(COLOR_WINDOW+1),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
&window_class);
|
||||
ATOM clazz = RegisterClassEx(&window_class);
|
||||
CHECK(clazz);
|
||||
registered = true;
|
||||
}
|
||||
|
||||
// Converts the WPARAM value passed to WM_MENUSELECT into an index
|
||||
// corresponding to the menu item that was selected.
|
||||
int GetMenuItemIndexFromWPARAM(HMENU menu, WPARAM w_param) const {
|
||||
int count = GetMenuItemCount(menu);
|
||||
// For normal command menu items, Windows passes a command id as the LOWORD
|
||||
// of WPARAM for WM_MENUSELECT. We need to walk forward through the menu
|
||||
// items to find an item with a matching ID. Ugh!
|
||||
for (int i = 0; i < count; ++i) {
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
mii.fMask = MIIM_ID;
|
||||
GetMenuItemInfo(menu, i, MF_BYPOSITION, &mii);
|
||||
if (mii.wID == w_param)
|
||||
return i;
|
||||
}
|
||||
// If we didn't find a matching command ID, this means a submenu has been
|
||||
// selected instead, and rather than passing a command ID in
|
||||
// LOWORD(w_param), Windows has actually passed us a position, so we just
|
||||
// return it.
|
||||
return w_param;
|
||||
}
|
||||
|
||||
NativeMenuWin::ItemData* GetItemData(ULONG_PTR item_data) {
|
||||
return reinterpret_cast<NativeMenuWin::ItemData*>(item_data);
|
||||
}
|
||||
|
||||
// Called as the user moves their mouse or arrows through the contents of the
|
||||
// menu.
|
||||
void OnMenuSelect(WPARAM w_param, HMENU menu) {
|
||||
if (!menu)
|
||||
return; // menu is null when closing on XP.
|
||||
|
||||
int position = GetMenuItemIndexFromWPARAM(menu, w_param);
|
||||
if (position >= 0)
|
||||
GetNativeMenuWinFromHMENU(menu)->model_->HighlightChangedTo(position);
|
||||
}
|
||||
|
||||
// Called by Windows to measure the size of an owner-drawn menu item.
|
||||
void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* measure_item_struct) {
|
||||
NativeMenuWin::ItemData* data = GetItemData(measure_item_struct->itemData);
|
||||
if (data) {
|
||||
gfx::Font font;
|
||||
measure_item_struct->itemWidth = font.GetStringWidth(data->label) +
|
||||
kIconWidth + kItemLeftMargin + kItemRightMargin -
|
||||
GetSystemMetrics(SM_CXMENUCHECK);
|
||||
if (data->submenu.get())
|
||||
measure_item_struct->itemWidth += kArrowWidth;
|
||||
// If the label contains an accelerator, make room for tab.
|
||||
if (data->label.find(L'\t') != string16::npos)
|
||||
measure_item_struct->itemWidth += font.GetStringWidth(L" ");
|
||||
measure_item_struct->itemHeight =
|
||||
font.GetHeight() + kItemBottomMargin + kItemTopMargin;
|
||||
} else {
|
||||
// Measure separator size.
|
||||
measure_item_struct->itemHeight = GetSystemMetrics(SM_CYMENU) / 2;
|
||||
measure_item_struct->itemWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by Windows to paint an owner-drawn menu item.
|
||||
void OnDrawItem(UINT w_param, DRAWITEMSTRUCT* draw_item_struct) {
|
||||
HDC dc = draw_item_struct->hDC;
|
||||
COLORREF prev_bg_color, prev_text_color;
|
||||
|
||||
// Set background color and text color
|
||||
if (draw_item_struct->itemState & ODS_SELECTED) {
|
||||
prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT));
|
||||
prev_text_color = SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT));
|
||||
} else {
|
||||
prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_MENU));
|
||||
if (draw_item_struct->itemState & ODS_DISABLED)
|
||||
prev_text_color = SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT));
|
||||
else
|
||||
prev_text_color = SetTextColor(dc, GetSysColor(COLOR_MENUTEXT));
|
||||
}
|
||||
|
||||
if (draw_item_struct->itemData) {
|
||||
NativeMenuWin::ItemData* data = GetItemData(draw_item_struct->itemData);
|
||||
// Draw the background.
|
||||
HBRUSH hbr = CreateSolidBrush(GetBkColor(dc));
|
||||
FillRect(dc, &draw_item_struct->rcItem, hbr);
|
||||
DeleteObject(hbr);
|
||||
|
||||
// Draw the label.
|
||||
RECT rect = draw_item_struct->rcItem;
|
||||
rect.top += kItemTopMargin;
|
||||
// Should we add kIconWidth only when icon.width() != 0 ?
|
||||
rect.left += kItemLeftMargin + kIconWidth;
|
||||
rect.right -= kItemRightMargin;
|
||||
UINT format = DT_TOP | DT_SINGLELINE;
|
||||
// Check whether the mnemonics should be underlined.
|
||||
BOOL underline_mnemonics;
|
||||
SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &underline_mnemonics, 0);
|
||||
if (!underline_mnemonics)
|
||||
format |= DT_HIDEPREFIX;
|
||||
gfx::Font font;
|
||||
HGDIOBJ old_font =
|
||||
static_cast<HFONT>(SelectObject(dc, font.GetNativeFont()));
|
||||
|
||||
// If an accelerator is specified (with a tab delimiting the rest of the
|
||||
// label from the accelerator), we have to justify the fist part on the
|
||||
// left and the accelerator on the right.
|
||||
// TODO(jungshik): This will break in RTL UI. Currently, he/ar use the
|
||||
// window system UI font and will not hit here.
|
||||
string16 label = data->label;
|
||||
string16 accel;
|
||||
string16::size_type tab_pos = label.find(L'\t');
|
||||
if (tab_pos != string16::npos) {
|
||||
accel = label.substr(tab_pos);
|
||||
label = label.substr(0, tab_pos);
|
||||
}
|
||||
DrawTextEx(dc, const_cast<wchar_t*>(label.data()),
|
||||
static_cast<int>(label.size()), &rect, format | DT_LEFT, NULL);
|
||||
if (!accel.empty())
|
||||
DrawTextEx(dc, const_cast<wchar_t*>(accel.data()),
|
||||
static_cast<int>(accel.size()), &rect,
|
||||
format | DT_RIGHT, NULL);
|
||||
SelectObject(dc, old_font);
|
||||
|
||||
ui::MenuModel::ItemType type =
|
||||
data->native_menu_win->model_->GetTypeAt(data->model_index);
|
||||
|
||||
// Draw the icon after the label, otherwise it would be covered
|
||||
// by the label.
|
||||
gfx::Image icon;
|
||||
if (data->native_menu_win->model_->GetIconAt(data->model_index, &icon)) {
|
||||
// We currently don't support items with both icons and checkboxes.
|
||||
const gfx::ImageSkia* skia_icon = icon.ToImageSkia();
|
||||
DCHECK(type != ui::MenuModel::TYPE_CHECK);
|
||||
gfx::Canvas canvas(
|
||||
skia_icon->GetRepresentation(ui::SCALE_FACTOR_100P),
|
||||
false);
|
||||
skia::DrawToNativeContext(
|
||||
canvas.sk_canvas(), dc,
|
||||
draw_item_struct->rcItem.left + kItemLeftMargin,
|
||||
draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom -
|
||||
draw_item_struct->rcItem.top - skia_icon->height()) / 2, NULL);
|
||||
} else if (type == ui::MenuModel::TYPE_CHECK &&
|
||||
data->native_menu_win->model_->IsItemCheckedAt(
|
||||
data->model_index)) {
|
||||
// Manually render a checkbox.
|
||||
ui::NativeThemeWin* native_theme = ui::NativeThemeWin::instance();
|
||||
const MenuConfig& config = MenuConfig::instance(native_theme);
|
||||
NativeTheme::State state;
|
||||
if (draw_item_struct->itemState & ODS_DISABLED) {
|
||||
state = NativeTheme::kDisabled;
|
||||
} else {
|
||||
state = draw_item_struct->itemState & ODS_SELECTED ?
|
||||
NativeTheme::kHovered : NativeTheme::kNormal;
|
||||
}
|
||||
int height =
|
||||
draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top;
|
||||
int icon_y = kItemTopMargin +
|
||||
(height - kItemTopMargin - kItemBottomMargin -
|
||||
config.check_height) / 2;
|
||||
gfx::Canvas canvas(gfx::Size(config.check_width, config.check_height),
|
||||
ui::SCALE_FACTOR_100P,
|
||||
false);
|
||||
NativeTheme::ExtraParams extra;
|
||||
extra.menu_check.is_radio = false;
|
||||
gfx::Rect bounds(0, 0, config.check_width, config.check_height);
|
||||
|
||||
// Draw the background and the check.
|
||||
native_theme->Paint(
|
||||
canvas.sk_canvas(), NativeTheme::kMenuCheckBackground,
|
||||
state, bounds, extra);
|
||||
native_theme->Paint(
|
||||
canvas.sk_canvas(), NativeTheme::kMenuCheck, state, bounds, extra);
|
||||
|
||||
// Draw checkbox to menu.
|
||||
skia::DrawToNativeContext(canvas.sk_canvas(), dc,
|
||||
draw_item_struct->rcItem.left + kItemLeftMargin,
|
||||
draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom -
|
||||
draw_item_struct->rcItem.top - config.check_height) / 2, NULL);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Draw the separator
|
||||
draw_item_struct->rcItem.top +=
|
||||
(draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top) / 3;
|
||||
DrawEdge(dc, &draw_item_struct->rcItem, EDGE_ETCHED, BF_TOP);
|
||||
}
|
||||
|
||||
SetBkColor(dc, prev_bg_color);
|
||||
SetTextColor(dc, prev_text_color);
|
||||
}
|
||||
|
||||
bool ProcessWindowMessage(HWND window,
|
||||
UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param,
|
||||
LRESULT* l_result) {
|
||||
switch (message) {
|
||||
case WM_MENUCOMMAND:
|
||||
OnMenuCommand(w_param, reinterpret_cast<HMENU>(l_param));
|
||||
*l_result = 0;
|
||||
return true;
|
||||
case WM_MENUSELECT:
|
||||
OnMenuSelect(LOWORD(w_param), reinterpret_cast<HMENU>(l_param));
|
||||
*l_result = 0;
|
||||
return true;
|
||||
case WM_MEASUREITEM:
|
||||
OnMeasureItem(w_param, reinterpret_cast<MEASUREITEMSTRUCT*>(l_param));
|
||||
*l_result = 0;
|
||||
return true;
|
||||
case WM_DRAWITEM:
|
||||
OnDrawItem(w_param, reinterpret_cast<DRAWITEMSTRUCT*>(l_param));
|
||||
*l_result = 0;
|
||||
return true;
|
||||
// TODO(beng): bring over owner draw from old menu system.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK MenuHostWindowProc(HWND window,
|
||||
UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param) {
|
||||
MenuHostWindow* host =
|
||||
reinterpret_cast<MenuHostWindow*>(gfx::GetWindowUserData(window));
|
||||
// host is null during initial construction.
|
||||
LRESULT l_result = 0;
|
||||
if (!host || !host->ProcessWindowMessage(window, message, w_param, l_param,
|
||||
&l_result)) {
|
||||
return DefWindowProc(window, message, w_param, l_param);
|
||||
}
|
||||
return l_result;
|
||||
}
|
||||
|
||||
HWND hwnd_;
|
||||
NativeMenuWin* parent_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MenuHostWindow);
|
||||
};
|
||||
|
||||
struct NativeMenuWin::HighlightedMenuItemInfo {
|
||||
HighlightedMenuItemInfo()
|
||||
: has_parent(false),
|
||||
has_submenu(false),
|
||||
menu(NULL),
|
||||
position(-1) {
|
||||
}
|
||||
|
||||
bool has_parent;
|
||||
bool has_submenu;
|
||||
|
||||
// The menu and position. These are only set for non-disabled menu items.
|
||||
NativeMenuWin* menu;
|
||||
int position;
|
||||
};
|
||||
|
||||
// static
|
||||
const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName =
|
||||
L"ViewsMenuHostWindow";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NativeMenuWin, public:
|
||||
|
||||
NativeMenuWin::NativeMenuWin(ui::MenuModel* model, HWND system_menu_for)
|
||||
: model_(model),
|
||||
menu_(NULL),
|
||||
owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL) &&
|
||||
!system_menu_for),
|
||||
system_menu_for_(system_menu_for),
|
||||
first_item_index_(0),
|
||||
menu_action_(MENU_ACTION_NONE),
|
||||
menu_to_select_(NULL),
|
||||
position_to_select_(-1),
|
||||
menu_to_select_factory_(this),
|
||||
parent_(NULL),
|
||||
destroyed_flag_(NULL),
|
||||
create_as_window_menu_(false) {
|
||||
}
|
||||
|
||||
NativeMenuWin::~NativeMenuWin() {
|
||||
if (destroyed_flag_)
|
||||
*destroyed_flag_ = true;
|
||||
STLDeleteContainerPointers(items_.begin(), items_.end());
|
||||
DestroyMenu(menu_);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NativeMenuWin, MenuWrapper implementation:
|
||||
|
||||
void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) {
|
||||
CreateHostWindow();
|
||||
UpdateStates();
|
||||
UINT flags = TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RECURSE;
|
||||
flags |= GetAlignmentFlags(alignment);
|
||||
menu_action_ = MENU_ACTION_NONE;
|
||||
|
||||
// Set a hook function so we can listen for keyboard events while the
|
||||
// menu is open, and store a pointer to this object in a static
|
||||
// variable so the hook has access to it (ugly, but it's the
|
||||
// only way).
|
||||
open_native_menu_win_ = this;
|
||||
HHOOK hhook = SetWindowsHookEx(WH_MSGFILTER, MenuMessageHook,
|
||||
GetModuleHandle(NULL), ::GetCurrentThreadId());
|
||||
|
||||
// Mark that any registered listeners have not been called for this particular
|
||||
// opening of the menu.
|
||||
listeners_called_ = false;
|
||||
|
||||
// Command dispatch is done through WM_MENUCOMMAND, handled by the host
|
||||
// window.
|
||||
HWND hwnd = host_window_->hwnd();
|
||||
menu_to_select_ = NULL;
|
||||
position_to_select_ = -1;
|
||||
menu_to_select_factory_.InvalidateWeakPtrs();
|
||||
bool destroyed = false;
|
||||
destroyed_flag_ = &destroyed;
|
||||
model_->MenuWillShow();
|
||||
TrackPopupMenu(menu_, flags, point.x(), point.y(), 0, host_window_->hwnd(),
|
||||
NULL);
|
||||
UnhookWindowsHookEx(hhook);
|
||||
open_native_menu_win_ = NULL;
|
||||
if (destroyed)
|
||||
return;
|
||||
destroyed_flag_ = NULL;
|
||||
if (menu_to_select_) {
|
||||
// Folks aren't too happy if we notify immediately. In particular, notifying
|
||||
// the delegate can cause destruction leaving the stack in a weird
|
||||
// state. Instead post a task, then notify. This mirrors what WM_MENUCOMMAND
|
||||
// does.
|
||||
menu_to_select_factory_.InvalidateWeakPtrs();
|
||||
base::MessageLoop::current()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&NativeMenuWin::DelayedSelect,
|
||||
menu_to_select_factory_.GetWeakPtr()));
|
||||
menu_action_ = MENU_ACTION_SELECTED;
|
||||
}
|
||||
// Send MenuClosed after we schedule the select, otherwise MenuClosed is
|
||||
// processed after the select (MenuClosed posts a delayed task too).
|
||||
model_->MenuClosed();
|
||||
}
|
||||
|
||||
void NativeMenuWin::CancelMenu() {
|
||||
EndMenu();
|
||||
}
|
||||
|
||||
void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) {
|
||||
ResetNativeMenu();
|
||||
items_.clear();
|
||||
|
||||
owner_draw_ = model_->HasIcons() || owner_draw_;
|
||||
first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : 0;
|
||||
for (int menu_index = first_item_index_;
|
||||
menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) {
|
||||
int model_index = menu_index - first_item_index_;
|
||||
if (model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SEPARATOR)
|
||||
AddSeparatorItemAt(menu_index, model_index);
|
||||
else
|
||||
AddMenuItemAt(menu_index, model_index);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMenuWin::UpdateStates() {
|
||||
// A depth-first walk of the menu items, updating states.
|
||||
int model_index = 0;
|
||||
std::vector<ItemData*>::const_iterator it;
|
||||
for (it = items_.begin(); it != items_.end(); ++it, ++model_index) {
|
||||
int menu_index = model_index + first_item_index_;
|
||||
SetMenuItemState(menu_index, model_->IsEnabledAt(model_index),
|
||||
model_->IsItemCheckedAt(model_index), false);
|
||||
if (model_->IsItemDynamicAt(model_index)) {
|
||||
// TODO(atwilson): Update the icon as well (http://crbug.com/66508).
|
||||
SetMenuItemLabel(menu_index, model_index,
|
||||
model_->GetLabelAt(model_index));
|
||||
}
|
||||
Menu2* submenu = (*it)->submenu.get();
|
||||
if (submenu)
|
||||
submenu->UpdateStates();
|
||||
}
|
||||
}
|
||||
|
||||
HMENU NativeMenuWin::GetNativeMenu() const {
|
||||
return menu_;
|
||||
}
|
||||
|
||||
NativeMenuWin::MenuAction NativeMenuWin::GetMenuAction() const {
|
||||
return menu_action_;
|
||||
}
|
||||
|
||||
void NativeMenuWin::AddMenuListener(MenuListener* listener) {
|
||||
listeners_.AddObserver(listener);
|
||||
}
|
||||
|
||||
void NativeMenuWin::RemoveMenuListener(MenuListener* listener) {
|
||||
listeners_.RemoveObserver(listener);
|
||||
}
|
||||
|
||||
void NativeMenuWin::SetMinimumWidth(int width) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void NativeMenuWin::OnMenuCommand(int position, HMENU menu) {
|
||||
host_window_->OnMenuCommand(position, menu);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NativeMenuWin, private:
|
||||
|
||||
// static
|
||||
NativeMenuWin* NativeMenuWin::open_native_menu_win_ = NULL;
|
||||
|
||||
void NativeMenuWin::DelayedSelect() {
|
||||
if (menu_to_select_)
|
||||
menu_to_select_->model_->ActivatedAt(position_to_select_);
|
||||
}
|
||||
|
||||
// static
|
||||
bool NativeMenuWin::GetHighlightedMenuItemInfo(
|
||||
HMENU menu,
|
||||
HighlightedMenuItemInfo* info) {
|
||||
for (int i = 0; i < ::GetMenuItemCount(menu); i++) {
|
||||
UINT state = ::GetMenuState(menu, i, MF_BYPOSITION);
|
||||
if (state & MF_HILITE) {
|
||||
if (state & MF_POPUP) {
|
||||
HMENU submenu = GetSubMenu(menu, i);
|
||||
if (GetHighlightedMenuItemInfo(submenu, info))
|
||||
info->has_parent = true;
|
||||
else
|
||||
info->has_submenu = true;
|
||||
} else if (!(state & MF_SEPARATOR) && !(state & MF_DISABLED)) {
|
||||
info->menu = GetNativeMenuWinFromHMENU(menu);
|
||||
info->position = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK NativeMenuWin::MenuMessageHook(
|
||||
int n_code, WPARAM w_param, LPARAM l_param) {
|
||||
LRESULT result = CallNextHookEx(NULL, n_code, w_param, l_param);
|
||||
|
||||
NativeMenuWin* this_ptr = open_native_menu_win_;
|
||||
if (!this_ptr)
|
||||
return result;
|
||||
|
||||
// The first time this hook is called, that means the menu has successfully
|
||||
// opened, so call the callback function on all of our listeners.
|
||||
if (!this_ptr->listeners_called_) {
|
||||
FOR_EACH_OBSERVER(MenuListener, this_ptr->listeners_, OnMenuOpened());
|
||||
this_ptr->listeners_called_ = true;
|
||||
}
|
||||
|
||||
MSG* msg = reinterpret_cast<MSG*>(l_param);
|
||||
if (msg->message == WM_LBUTTONUP || msg->message == WM_RBUTTONUP) {
|
||||
HighlightedMenuItemInfo info;
|
||||
if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info) && info.menu) {
|
||||
// It appears that when running a menu by way of TrackPopupMenu(Ex) win32
|
||||
// gets confused if the underlying window paints itself. As its very easy
|
||||
// for the underlying window to repaint itself (especially since some menu
|
||||
// items trigger painting of the tabstrip on mouse over) we have this
|
||||
// workaround. When the mouse is released on a menu item we remember the
|
||||
// menu item and end the menu. When the nested message loop returns we
|
||||
// schedule a task to notify the model. It's still possible to get a
|
||||
// WM_MENUCOMMAND, so we have to be careful that we don't notify the model
|
||||
// twice.
|
||||
this_ptr->menu_to_select_ = info.menu;
|
||||
this_ptr->position_to_select_ = info.position;
|
||||
EndMenu();
|
||||
}
|
||||
} else if (msg->message == WM_KEYDOWN) {
|
||||
HighlightedMenuItemInfo info;
|
||||
if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info)) {
|
||||
if (msg->wParam == VK_LEFT && !info.has_parent) {
|
||||
this_ptr->menu_action_ = MENU_ACTION_PREVIOUS;
|
||||
::EndMenu();
|
||||
} else if (msg->wParam == VK_RIGHT && !info.has_parent &&
|
||||
!info.has_submenu) {
|
||||
this_ptr->menu_action_ = MENU_ACTION_NEXT;
|
||||
::EndMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const {
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
mii.fMask = MIIM_FTYPE;
|
||||
GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
|
||||
return !!(mii.fType & MF_SEPARATOR);
|
||||
}
|
||||
|
||||
void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) {
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA;
|
||||
if (!owner_draw_)
|
||||
mii.fType = MFT_STRING;
|
||||
else
|
||||
mii.fType = MFT_OWNERDRAW;
|
||||
|
||||
ItemData* item_data = new ItemData;
|
||||
item_data->label = string16();
|
||||
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
|
||||
if (type == ui::MenuModel::TYPE_SUBMENU) {
|
||||
item_data->submenu.reset(new Menu2(model_->GetSubmenuModelAt(model_index)));
|
||||
mii.fMask |= MIIM_SUBMENU;
|
||||
mii.hSubMenu = item_data->submenu->GetNativeMenu();
|
||||
GetNativeMenuWinFromHMENU(mii.hSubMenu)->parent_ = this;
|
||||
} else {
|
||||
if (type == ui::MenuModel::TYPE_RADIO)
|
||||
mii.fType |= MFT_RADIOCHECK;
|
||||
mii.wID = model_->GetCommandIdAt(model_index);
|
||||
}
|
||||
item_data->native_menu_win = this;
|
||||
item_data->model_index = model_index;
|
||||
items_.insert(items_.begin() + model_index, item_data);
|
||||
mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data);
|
||||
UpdateMenuItemInfoForString(&mii, model_index,
|
||||
model_->GetLabelAt(model_index));
|
||||
InsertMenuItem(menu_, menu_index, TRUE, &mii);
|
||||
}
|
||||
|
||||
void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) {
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
mii.fMask = MIIM_FTYPE;
|
||||
mii.fType = MFT_SEPARATOR;
|
||||
// Insert a dummy entry into our label list so we can index directly into it
|
||||
// using item indices if need be.
|
||||
items_.insert(items_.begin() + model_index, new ItemData);
|
||||
InsertMenuItem(menu_, menu_index, TRUE, &mii);
|
||||
}
|
||||
|
||||
void NativeMenuWin::SetMenuItemState(int menu_index, bool enabled, bool checked,
|
||||
bool is_default) {
|
||||
if (IsSeparatorItemAt(menu_index))
|
||||
return;
|
||||
|
||||
UINT state = enabled ? MFS_ENABLED : MFS_DISABLED;
|
||||
if (checked)
|
||||
state |= MFS_CHECKED;
|
||||
if (is_default)
|
||||
state |= MFS_DEFAULT;
|
||||
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
mii.fMask = MIIM_STATE;
|
||||
mii.fState = state;
|
||||
SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
|
||||
}
|
||||
|
||||
void NativeMenuWin::SetMenuItemLabel(int menu_index,
|
||||
int model_index,
|
||||
const string16& label) {
|
||||
if (IsSeparatorItemAt(menu_index))
|
||||
return;
|
||||
|
||||
MENUITEMINFO mii = {0};
|
||||
mii.cbSize = sizeof(mii);
|
||||
UpdateMenuItemInfoForString(&mii, model_index, label);
|
||||
SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
|
||||
}
|
||||
|
||||
void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
|
||||
int model_index,
|
||||
const string16& label) {
|
||||
string16 formatted = label;
|
||||
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
|
||||
// Strip out any tabs, otherwise they get interpreted as accelerators and can
|
||||
// lead to weird behavior.
|
||||
ReplaceSubstringsAfterOffset(&formatted, 0, L"\t", L" ");
|
||||
if (type != ui::MenuModel::TYPE_SUBMENU) {
|
||||
// Add accelerator details to the label if provided.
|
||||
ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
|
||||
if (model_->GetAcceleratorAt(model_index, &accelerator)) {
|
||||
formatted += L"\t";
|
||||
formatted += accelerator.GetShortcutText();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the owned string, since Windows will want us to keep this new
|
||||
// version around.
|
||||
items_[model_index]->label = formatted;
|
||||
|
||||
// Give Windows a pointer to the label string.
|
||||
mii->fMask |= MIIM_STRING;
|
||||
mii->dwTypeData =
|
||||
const_cast<wchar_t*>(items_[model_index]->label.c_str());
|
||||
}
|
||||
|
||||
UINT NativeMenuWin::GetAlignmentFlags(int alignment) const {
|
||||
UINT alignment_flags = TPM_TOPALIGN;
|
||||
if (alignment == Menu2::ALIGN_TOPLEFT)
|
||||
alignment_flags |= TPM_LEFTALIGN;
|
||||
else if (alignment == Menu2::ALIGN_TOPRIGHT)
|
||||
alignment_flags |= TPM_RIGHTALIGN;
|
||||
return alignment_flags;
|
||||
}
|
||||
|
||||
void NativeMenuWin::ResetNativeMenu() {
|
||||
if (IsWindow(system_menu_for_)) {
|
||||
if (menu_)
|
||||
GetSystemMenu(system_menu_for_, TRUE);
|
||||
menu_ = GetSystemMenu(system_menu_for_, FALSE);
|
||||
} else {
|
||||
if (menu_)
|
||||
DestroyMenu(menu_);
|
||||
menu_ = create_as_window_menu_ ? CreateMenu() : CreatePopupMenu();
|
||||
// Rather than relying on the return value of TrackPopupMenuEx, which is
|
||||
// always a command identifier, instead we tell the menu to notify us via
|
||||
// our host window and the WM_MENUCOMMAND message.
|
||||
MENUINFO mi = {0};
|
||||
mi.cbSize = sizeof(mi);
|
||||
mi.fMask = MIM_STYLE | MIM_MENUDATA;
|
||||
mi.dwStyle = MNS_NOTIFYBYPOS;
|
||||
mi.dwMenuData = reinterpret_cast<ULONG_PTR>(this);
|
||||
SetMenuInfo(menu_, &mi);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMenuWin::CreateHostWindow() {
|
||||
// This only gets called from RunMenuAt, and as such there is only ever one
|
||||
// host window per menu hierarchy, no matter how many NativeMenuWin objects
|
||||
// exist wrapping submenus.
|
||||
if (!host_window_.get())
|
||||
host_window_.reset(new MenuHostWindow(this));
|
||||
}
|
||||
|
||||
} // namespace atom
|
192
atom/browser/ui/win/native_menu_win.h
Normal file
192
atom/browser/ui/win/native_menu_win.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
// 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 BROWSER_UI_WIN_NATIVE_MENU_WIN_H_
|
||||
#define BROWSER_UI_WIN_NATIVE_MENU_WIN_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/memory/scoped_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/strings/string16.h"
|
||||
|
||||
namespace gfx {
|
||||
class Point;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace views {
|
||||
class MenuInsertionDelegateWin;
|
||||
class MenuListener;
|
||||
}
|
||||
|
||||
namespace atom {
|
||||
|
||||
class NativeMenuWin {
|
||||
public:
|
||||
// All of the possible actions that can result from RunMenuAt.
|
||||
enum MenuAction {
|
||||
MENU_ACTION_NONE, // Menu cancelled, or never opened.
|
||||
MENU_ACTION_SELECTED, // An item was selected.
|
||||
MENU_ACTION_PREVIOUS, // User wants to navigate to the previous menu.
|
||||
MENU_ACTION_NEXT, // User wants to navigate to the next menu.
|
||||
};
|
||||
|
||||
// Construct a NativeMenuWin, with a model and delegate. If |system_menu_for|
|
||||
// is non-NULL, the NativeMenuWin wraps the system menu for that window.
|
||||
// The caller owns the model and the delegate.
|
||||
NativeMenuWin(ui::MenuModel* model, HWND system_menu_for);
|
||||
virtual ~NativeMenuWin();
|
||||
|
||||
void RunMenuAt(const gfx::Point& point, int alignment);
|
||||
void CancelMenu();
|
||||
void Rebuild(views::MenuInsertionDelegateWin* delegate);
|
||||
void UpdateStates();
|
||||
HMENU GetNativeMenu() const;
|
||||
MenuAction GetMenuAction() const;
|
||||
void AddMenuListener(views::MenuListener* listener);
|
||||
void RemoveMenuListener(views::MenuListener* listener);
|
||||
void SetMinimumWidth(int width);
|
||||
|
||||
// Called by user to generate a menu command event.
|
||||
void OnMenuCommand(int position, HMENU menu);
|
||||
|
||||
// Flag to create a window menu instead of popup menu.
|
||||
void set_create_as_window_menu(bool flag) { create_as_window_menu_ = flag; }
|
||||
bool create_as_window_menu() const { return create_as_window_menu_; }
|
||||
|
||||
private:
|
||||
// IMPORTANT: Note about indices.
|
||||
// Functions in this class deal in two index spaces:
|
||||
// 1. menu_index - the index of an item within the actual Windows
|
||||
// native menu.
|
||||
// 2. model_index - the index of the item within our model.
|
||||
// These two are most often but not always the same value! The
|
||||
// notable exception is when this object is used to wrap the
|
||||
// Windows System Menu. In this instance, the model indices start
|
||||
// at 0, but the insertion index into the existing menu is not.
|
||||
// It is important to take this into consideration when editing the
|
||||
// code in the functions in this class.
|
||||
|
||||
struct HighlightedMenuItemInfo;
|
||||
|
||||
// Returns true if the item at the specified index is a separator.
|
||||
bool IsSeparatorItemAt(int menu_index) const;
|
||||
|
||||
// Add items. See note above about indices.
|
||||
void AddMenuItemAt(int menu_index, int model_index);
|
||||
void AddSeparatorItemAt(int menu_index, int model_index);
|
||||
|
||||
// Sets the state of the item at the specified index.
|
||||
void SetMenuItemState(int menu_index,
|
||||
bool enabled,
|
||||
bool checked,
|
||||
bool is_default);
|
||||
|
||||
// Sets the label of the item at the specified index.
|
||||
void SetMenuItemLabel(int menu_index,
|
||||
int model_index,
|
||||
const string16& label);
|
||||
|
||||
// Updates the local data structure with the correctly formatted version of
|
||||
// |label| at the specified model_index, and adds string data to |mii| if
|
||||
// the menu is not owner-draw. That's a mouthful. This function exists because
|
||||
// of the peculiarities of the Windows menu API.
|
||||
void UpdateMenuItemInfoForString(MENUITEMINFO* mii,
|
||||
int model_index,
|
||||
const string16& label);
|
||||
|
||||
// Returns the alignment flags to be passed to TrackPopupMenuEx, based on the
|
||||
// supplied alignment and the UI text direction.
|
||||
UINT GetAlignmentFlags(int alignment) const;
|
||||
|
||||
// Resets the native menu stored in |menu_| by destroying any old menu then
|
||||
// creating a new empty one.
|
||||
void ResetNativeMenu();
|
||||
|
||||
// Creates the host window that receives notifications from the menu.
|
||||
void CreateHostWindow();
|
||||
|
||||
// Callback from task to notify menu it was selected.
|
||||
void DelayedSelect();
|
||||
|
||||
// Given a menu that's currently popped-up, find the currently highlighted
|
||||
// item. Returns true if a highlighted item was found.
|
||||
static bool GetHighlightedMenuItemInfo(HMENU menu,
|
||||
HighlightedMenuItemInfo* info);
|
||||
|
||||
// Hook to receive keyboard events while the menu is open.
|
||||
static LRESULT CALLBACK MenuMessageHook(
|
||||
int n_code, WPARAM w_param, LPARAM l_param);
|
||||
|
||||
// Our attached model and delegate.
|
||||
ui::MenuModel* model_;
|
||||
|
||||
HMENU menu_;
|
||||
|
||||
// True if the contents of menu items in this menu are drawn by the menu host
|
||||
// window, rather than Windows.
|
||||
bool owner_draw_;
|
||||
|
||||
// An object that collects all of the data associated with an individual menu
|
||||
// item.
|
||||
struct ItemData;
|
||||
std::vector<ItemData*> items_;
|
||||
|
||||
// The window that receives notifications from the menu.
|
||||
class MenuHostWindow;
|
||||
friend MenuHostWindow;
|
||||
scoped_ptr<MenuHostWindow> host_window_;
|
||||
|
||||
// The HWND this menu is the system menu for, or NULL if the menu is not a
|
||||
// system menu.
|
||||
HWND system_menu_for_;
|
||||
|
||||
// The index of the first item in the model in the menu.
|
||||
int first_item_index_;
|
||||
|
||||
// The action that took place during the call to RunMenuAt.
|
||||
MenuAction menu_action_;
|
||||
|
||||
// A list of listeners to call when the menu opens.
|
||||
ObserverList<views::MenuListener> listeners_;
|
||||
|
||||
// Keep track of whether the listeners have already been called at least
|
||||
// once.
|
||||
bool listeners_called_;
|
||||
|
||||
// See comment in MenuMessageHook for details on these.
|
||||
NativeMenuWin* menu_to_select_;
|
||||
int position_to_select_;
|
||||
base::WeakPtrFactory<NativeMenuWin> menu_to_select_factory_;
|
||||
|
||||
// If we're a submenu, this is our parent.
|
||||
NativeMenuWin* parent_;
|
||||
|
||||
// If non-null the destructor sets this to true. This is set to non-null while
|
||||
// the menu is showing. It is used to detect if the menu was deleted while
|
||||
// running.
|
||||
bool* destroyed_flag_;
|
||||
|
||||
// Ugly: a static pointer to the instance of this class that currently
|
||||
// has a menu open, because our hook function that receives keyboard
|
||||
// events doesn't have a mechanism to get a user data pointer.
|
||||
static NativeMenuWin* open_native_menu_win_;
|
||||
|
||||
// Create as window menu.
|
||||
bool create_as_window_menu_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeMenuWin);
|
||||
};
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // BROWSER_UI_WIN_NATIVE_MENU_WIN_H_
|
Loading…
Add table
Add a link
Reference in a new issue