Add a hotkey settings control and use it in FancyZones (#450)

Add a hotkey settings control and use it in FancyZones
This commit is contained in:
Bartosz Sosnowski 2019-10-02 17:18:55 +02:00 committed by GitHub
parent de08485db8
commit 64f606daaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 239 additions and 18 deletions

View File

@ -95,6 +95,20 @@ namespace PowerToysSettings {
m_json.as_object()[L"properties"].as_object()[name] = item;
}
void Settings::add_hotkey(const std::wstring& name, UINT description_resource_id, const HotkeyObject& hotkey) {
add_hotkey(name, get_resource(description_resource_id), hotkey);
}
void Settings::add_hotkey(const std::wstring& name, const std::wstring& description, const HotkeyObject& hotkey) {
web::json::value item = web::json::value::object();
item.as_object()[L"display_name"] = web::json::value::string(description);
item.as_object()[L"editor_type"] = web::json::value::string(L"hotkey");
item.as_object()[L"value"] = hotkey.get_json();
item.as_object()[L"order"] = web::json::value::number(++m_curr_priority);
m_json.as_object()[L"properties"].as_object()[name] = item;
}
// add_custom_action overloads.
void Settings::add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, UINT ext_description_resource_id) {
add_custom_action(name, get_resource(description_resource_id), get_resource(button_text_resource_id), get_resource(ext_description_resource_id));
@ -188,6 +202,11 @@ namespace PowerToysSettings {
m_json.as_object()[L"properties"].as_object()[name] = add_property_generic(name, value);
};
template <>
void PowerToyValues::add_property(const std::wstring& name, HotkeyObject value) {
m_json.as_object()[L"properties"].as_object()[name] = add_property_generic(name, value.get_json());
};
bool PowerToyValues::is_bool_value(const std::wstring& property_name) {
return m_json.is_object() &&
m_json.has_object_field(L"properties") &&
@ -209,6 +228,13 @@ namespace PowerToysSettings {
m_json[L"properties"][property_name].has_string_field(L"value");
}
bool PowerToyValues::is_object_value(const std::wstring& property_name) {
return m_json.is_object() &&
m_json.has_object_field(L"properties") &&
m_json[L"properties"].has_object_field(property_name) &&
m_json[L"properties"][property_name].has_object_field(L"value");
}
bool PowerToyValues::get_bool_value(const std::wstring& property_name) {
return m_json[L"properties"][property_name][L"value"].as_bool();
}
@ -221,6 +247,10 @@ namespace PowerToysSettings {
return m_json[L"properties"][property_name][L"value"].as_string();
}
web::json::value PowerToyValues::get_json(const std::wstring& property_name) {
return m_json[L"properties"][property_name][L"value"];
}
std::wstring PowerToyValues::serialize() {
set_version();
return m_json.serialize();

View File

@ -4,6 +4,8 @@
namespace PowerToysSettings {
class HotkeyObject;
class Settings {
public:
Settings(
@ -32,14 +34,18 @@ namespace PowerToysSettings {
void add_color_picker(const std::wstring& name, UINT description_resource_id, const std::wstring& value);
void add_color_picker(const std::wstring& name, const std::wstring& description, const std::wstring& value);
void add_hotkey(const std::wstring& name, UINT description_resource_id, const HotkeyObject& hotkey);
void add_hotkey(const std::wstring& name, const std::wstring& description, const HotkeyObject& hotkey);
void add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, UINT ext_description_resource_id);
void add_custom_action(const std::wstring& name, UINT description_resource_id, UINT button_text_resource_id, const std::wstring& value);
void add_custom_action(const std::wstring& name, const std::wstring& description, const std::wstring& button_text, const std::wstring& value);
// Serialize the internal json to a string.
std::wstring serialize();
// Serialize the internal json to the input buffer.
bool serialize_to_buffer(wchar_t* buffer, int *buffer_size);
bool serialize_to_buffer(wchar_t* buffer, int* buffer_size);
private:
web::json::value m_json;
@ -62,11 +68,13 @@ namespace PowerToysSettings {
bool is_bool_value(const std::wstring& property_name);
bool is_int_value(const std::wstring& property_name);
bool is_string_value(const std::wstring& property_name);
bool is_object_value(const std::wstring& property_name);
// Get property value
bool get_bool_value(const std::wstring& property_name);
int get_int_value(const std::wstring& property_name);
std::wstring get_string_value(const std::wstring& property_name);
web::json::value get_json(const std::wstring& property_name);
std::wstring serialize();
void save_to_settings_file();
@ -93,4 +101,46 @@ namespace PowerToysSettings {
CustomActionObject(web::json::value action_json) : m_json(action_json) {};
web::json::value m_json;
};
class HotkeyObject {
public:
static HotkeyObject from_json(web::json::value json) {
return HotkeyObject(json);
}
static HotkeyObject from_json_string(const std::wstring& json) {
web::json::value parsed_json = web::json::value::parse(json);
return HotkeyObject(parsed_json);
}
static HotkeyObject from_settings(bool win_pressed, bool ctrl_pressed, bool alt_pressed, bool shift_pressed, UINT vk_code, const std::wstring& key) {
web::json::value json = web::json::value::object();
json.as_object()[L"win"] = web::json::value::boolean(win_pressed);
json.as_object()[L"ctrl"] = web::json::value::boolean(ctrl_pressed);
json.as_object()[L"alt"] = web::json::value::boolean(alt_pressed);
json.as_object()[L"shift"] = web::json::value::boolean(shift_pressed);
json.as_object()[L"code"] = web::json::value::number(vk_code);
json.as_object()[L"key"] = web::json::value::string(key);
return HotkeyObject(json);
}
const web::json::value& get_json() const { return m_json; }
std::wstring get_key() { return m_json[L"key"].as_string(); }
UINT get_code() { return m_json[L"code"].as_integer(); }
bool win_pressed() { return m_json[L"win"].as_bool(); }
bool ctrl_pressed() { return m_json[L"ctrl"].as_bool(); }
bool alt_pressed() { return m_json[L"alt"].as_bool(); }
bool shift_pressed() { return m_json[L"shift"].as_bool(); }
UINT get_modifiers_repeat() {
return (win_pressed() ? MOD_WIN : 0) |
(ctrl_pressed() ? MOD_CONTROL : 0) |
(alt_pressed() ? MOD_ALT : 0) |
(shift_pressed() ? MOD_SHIFT : 0);
}
UINT get_modifiers() {
return get_modifiers_repeat() | MOD_NOREPEAT;
}
protected:
HotkeyObject(web::json::value hotkey_json) : m_json(hotkey_json) {};
web::json::value m_json;
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@ public:
IFACEMETHODIMP_(void) WindowCreated(HWND window) noexcept;
IFACEMETHODIMP_(bool) OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept;
IFACEMETHODIMP_(void) ToggleEditor() noexcept;
IFACEMETHODIMP_(void) SettingsChanged() noexcept;
// IZoneWindowHost
IFACEMETHODIMP_(void) ToggleZoneViewers() noexcept;
@ -117,7 +118,7 @@ IFACEMETHODIMP_(void) FancyZones::Run() noexcept
m_window = CreateWindowExW(WS_EX_TOOLWINDOW, L"SuperFancyZones", L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, this);
if (!m_window) return;
RegisterHotKey(m_window, 1, MOD_WIN, VK_OEM_3);
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
VirtualDesktopChanged();
}
@ -328,6 +329,13 @@ void FancyZones::ToggleEditor() noexcept
waitForEditorThread.detach();
}
void FancyZones::SettingsChanged() noexcept
{
// Update the hotkey
UnregisterHotKey(m_window, 1);
RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code());
}
// IZoneWindowHost
IFACEMETHODIMP_(void) FancyZones::ToggleZoneViewers() noexcept
{

View File

@ -52,7 +52,7 @@ private:
} m_configStrings[1] = {
{ L"fancyzones_zoneHighlightColor", &m_settings.zoneHightlightColor, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR },
};
const std::wstring m_editor_hotkey_name = L"fancyzones_editor_hotkey";
};
IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int *buffer_size) noexcept
@ -73,6 +73,7 @@ IFACEMETHODIMP_(bool) FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ in
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION
);
settings.add_hotkey(m_editor_hotkey_name, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey);
for (auto const& setting : m_configBools)
{
@ -91,6 +92,7 @@ IFACEMETHODIMP_(void) FancyZonesSettings::SetConfig(PCWSTR config) noexcept try
{
LoadSettings(config, false /*fromFile*/);
SaveSettings();
m_callback->SettingsChanged();
Trace::SettingsChanged(m_settings);
}
CATCH_LOG();
@ -129,6 +131,11 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept try
*setting.value = values.get_string_value(setting.name);
}
}
if (values.is_object_value(m_editor_hotkey_name))
{
m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(values.get_json(m_editor_hotkey_name));
}
}
CATCH_LOG();
@ -146,6 +153,8 @@ void FancyZonesSettings::SaveSettings() noexcept try
values.add_property(setting.name, *setting.value);
}
values.add_property(m_editor_hotkey_name, m_settings.editorHotkey);
values.save_to_settings_file();
}
CATCH_LOG();

View File

@ -1,6 +1,7 @@
#pragma once
#define ZONE_STAMP L"FancyZones_zone"
#include <common/settings_objects.h>
struct Settings
{
@ -15,6 +16,7 @@ struct Settings
bool use_standalone_editor = true;
bool use_cursorpos_editor_startupscreen = true;
std::wstring zoneHightlightColor = L"#0078D7";
PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3, L"~");
};
interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZonesSettings : public IUnknown

View File

@ -17,7 +17,8 @@
IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS "Move newly created windows to their last known zone"
IDS_SETTING_LAUNCH_EDITOR_LABEL "Zone configuration"
IDS_SETTING_LAUNCH_EDITOR_BUTTON "Edit zones"
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION "To launch the zone editor, select the Edit zones button below or press Win + ~ anytime."
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION "To launch the zone editor, select the Edit zones button below or press the zone editor hotkey anytime"
IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL "Configure the zone editor hotkey"
END
1 VERSIONINFO

View File

@ -12,3 +12,4 @@
#define IDS_SETTING_LAUNCH_EDITOR_LABEL 112
#define IDS_SETTING_LAUNCH_EDITOR_BUTTON 113
#define IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION 114
#define IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL 115

View File

@ -59,7 +59,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\..\..\common\Telemetry;..\..\..\..\;..\..\..\..\deps\cpprestsdk\include;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\..\common\Telemetry;..\..\..\..\;..\..\..\..\..\deps\cpprestsdk\include;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@ -80,7 +80,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\..\..\common\Telemetry;..\..\..\..\;..\..\..\..\deps\cpprestsdk\include;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\..\..\common\Telemetry;..\..\..\..\;..\..\..\..\..\deps\cpprestsdk\include;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>

View File

@ -5,6 +5,7 @@ import {StringTextSettingsControl} from './StringTextSettingsControl';
import {IntSpinnerSettingsControl} from './IntSpinnerSettingsControl';
import {ColorPickerSettingsControl} from './ColorPickerSettingsControl';
import {CustomActionSettingsControl} from './CustomActionSettingsControl';
import {HotkeySettingsControl} from './HotkeySettingsControl';
export class CustomSettingsScreen extends React.Component <any, any> {
references: any;
@ -138,6 +139,13 @@ export class CustomSettingsScreen extends React.Component <any, any> {
key={key}
ref={(input) => {this.references[key]=input;}}
/>;
case 'hotkey':
return <HotkeySettingsControl
setting = {power_toys_properties[key]}
key={key}
on_change={this.parent_on_change}
ref={(input) => {this.references[key]=input;}}
/>;
default:
return null;
}

View File

@ -0,0 +1,112 @@
import React from 'react';
import {BaseSettingsControl} from './BaseSettingsControl';
import { TextField } from 'office-ui-fabric-react';
function makeDisplayValue(value : any) : string {
if (!value) {
return '(none)';
}
let keyparts = [];
if (value.win) {
keyparts.push('Win');
}
if (value.ctrl) {
keyparts.push('Ctrl');
}
if (value.alt) {
keyparts.push('Alt');
}
if (value.shift) {
keyparts.push('Shift');
}
keyparts.push(value.key);
return keyparts.join(' + ');
}
export class HotkeySettingsControl extends BaseSettingsControl {
textref:any = null; // Keeps a reference to the corresponding TextField in the DOM.
constructor(props:any) {
super(props);
this.textref = null;
this.state = {
property_values: props.setting,
display_value : makeDisplayValue(props.setting.value)
}
}
componentWillReceiveProps(props: any) {
// Fully controlled component.
// Reacting to a property change so that the control is redrawn properly.
this.setState({ property_values: props.setting })
}
public get_value() : any {
// Returns the TextField value.
return {value: this.state.property_values.value};
}
public render(): JSX.Element {
// Renders a UI Fabric TextField.
return (
<TextField
styles= {{
root: {
width: '350px',
alignSelf: 'start'
}}}
onKeyDown = {
(_event) => {
_event.preventDefault();
if (_event.key === 'Meta' ||
_event.key === 'Control' ||
_event.key === 'Shift' ||
_event.key === 'Alt') {
return;
}
let new_value = {
win : _event.metaKey,
ctrl : _event.ctrlKey,
alt : _event.altKey,
shift : _event.shiftKey,
key : _event.key,
code : _event.keyCode,
};
if (new_value.key === ' ') {
new_value.key = 'Space';
}
if (new_value.key === '~~') {
new_value.key = '~';
}
if (!new_value.key || new_value.key === 'Unidentified') {
switch (new_value.code) {
case 192:
new_value.key = '~';
break;
default:
new_value.key = `(Key ${new_value.code})`;
}
}
if (new_value.key.length === 1) {
new_value.key = new_value.key.toLocaleUpperCase();
}
this.setState( (prev_state:any) => ({
property_values: {
...(prev_state.property_values),
value: new_value
},
display_value: makeDisplayValue(new_value)
})
);
this.parent_on_change();
}
}
onKeyUp = {() => {}}
value={this.state.display_value}
label={this.state.property_values.display_name}
componentRef= {(input) => {this.textref=input;}}
/>
);
}
}