Varias mejoras como auto asignar el foco si la aplaicacion ya esta ejecutandose. Modo transparente. Funcion de Pinned .
This commit is contained in:
commit
d3dfec8d95
|
@ -0,0 +1,418 @@
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||||
|
bld/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Build results on 'Bin' directories
|
||||||
|
**/[Bb]in/*
|
||||||
|
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||||
|
# (https://github.com/github/gitignore/pull/3736)
|
||||||
|
#!**/[Bb]in/*.refresh
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
*.trx
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Approval Tests result files
|
||||||
|
*.received.*
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.idb
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||||
|
!Directory.Build.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
**/.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
**/.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
**/.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
**/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
#tools/**
|
||||||
|
#!tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
MSBuild_Logs/
|
||||||
|
|
||||||
|
# AWS SAM Build and Temporary Artifacts folder
|
||||||
|
.aws-sam
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
**/.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
**/.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
**/.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||||
|
// Use hover for the description of the existing attributes
|
||||||
|
// For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/net8.0-windows/ShortcutsHelper.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/ShortcutsHelper.sln",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/ShortcutsHelper.sln",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"--project",
|
||||||
|
"${workspaceFolder}/ShortcutsHelper.sln"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Application x:Class="ShortcutsHelper.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:ShortcutsHelper"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : System.Windows.Application
|
||||||
|
{
|
||||||
|
private static Mutex? _mutex = null;
|
||||||
|
private const string MutexName = "ShortcutsHelper_SingleInstance_Mutex";
|
||||||
|
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
// Intentar crear un mutex para controlar instancia única
|
||||||
|
bool createdNew;
|
||||||
|
_mutex = new Mutex(true, MutexName, out createdNew);
|
||||||
|
|
||||||
|
if (!createdNew)
|
||||||
|
{
|
||||||
|
// Ya hay una instancia ejecutándose
|
||||||
|
// Intentar activar la ventana existente
|
||||||
|
ActivateExistingWindow();
|
||||||
|
|
||||||
|
// Cerrar esta instancia
|
||||||
|
Shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primera instancia, continuar normalmente
|
||||||
|
base.OnStartup(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnExit(ExitEventArgs e)
|
||||||
|
{
|
||||||
|
// Liberar el mutex al salir
|
||||||
|
_mutex?.ReleaseMutex();
|
||||||
|
_mutex?.Dispose();
|
||||||
|
base.OnExit(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActivateExistingWindow()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Buscar el proceso de ShortcutsHelper que ya está ejecutándose
|
||||||
|
var currentProcess = Process.GetCurrentProcess();
|
||||||
|
var processes = Process.GetProcessesByName(currentProcess.ProcessName);
|
||||||
|
|
||||||
|
foreach (var process in processes)
|
||||||
|
{
|
||||||
|
// Saltar el proceso actual
|
||||||
|
if (process.Id == currentProcess.Id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Intentar activar la ventana principal del proceso
|
||||||
|
if (process.MainWindowHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Si la ventana está minimizada, restaurarla
|
||||||
|
if (IsIconic(process.MainWindowHandle))
|
||||||
|
{
|
||||||
|
ShowWindow(process.MainWindowHandle, SW_RESTORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traer al frente y dar foco
|
||||||
|
SetForegroundWindow(process.MainWindowHandle);
|
||||||
|
BringWindowToTop(process.MainWindowHandle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Si falla la activación, al menos mostrar un mensaje
|
||||||
|
System.Windows.MessageBox.Show($"ShortcutsHelper ya está ejecutándose. Error al activar: {ex.Message}",
|
||||||
|
"Aplicación ya en ejecución",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIs de Windows para manejo de ventanas
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool BringWindowToTop(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool IsIconic(IntPtr hWnd);
|
||||||
|
|
||||||
|
private const int SW_RESTORE = 9;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper
|
||||||
|
{
|
||||||
|
public class BoolToPinConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool isPinned)
|
||||||
|
{
|
||||||
|
return isPinned ? "📌" : "📍";
|
||||||
|
}
|
||||||
|
return "📍";
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BoolToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
return Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is Visibility visibility)
|
||||||
|
{
|
||||||
|
return visibility == Visibility.Visible;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
<Window x:Class="ShortcutsHelper.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:ShortcutsHelper"
|
||||||
|
xmlns:vm="clr-namespace:ShortcutsHelper.ViewModels"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Shortcuts Helper" Height="400" Width="600" Topmost="True"
|
||||||
|
ResizeMode="CanResize" WindowStyle="ToolWindow">
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Converter para el ícono de Pin -->
|
||||||
|
<local:BoolToPinConverter x:Key="BoolToPinConverter"/>
|
||||||
|
<!-- Converter para visibilidad basada en boolean -->
|
||||||
|
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
|
</Window.Resources>
|
||||||
|
<Window.DataContext>
|
||||||
|
<vm:MainViewModel/>
|
||||||
|
</Window.DataContext>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Header con aplicación actual y botón de Pin -->
|
||||||
|
<Border Grid.Row="0" Background="DarkBlue" Padding="8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Botón de Pin -->
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
x:Name="PinButton"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderThickness="0"
|
||||||
|
Foreground="White"
|
||||||
|
FontSize="16"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
ToolTip="Pin/Unpin aplicación actual"
|
||||||
|
Click="PinButton_Click">
|
||||||
|
<Button.Content>
|
||||||
|
<TextBlock Text="{Binding IsPinnedMode, Converter={StaticResource BoolToPinConverter}}" />
|
||||||
|
</Button.Content>
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4000BFFF"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Información de la aplicación -->
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<TextBlock Text="App:" FontWeight="Bold" Foreground="White" Margin="0,0,5,0"/>
|
||||||
|
<TextBlock Text="{Binding CurrentApplication}" FontWeight="Bold" Foreground="Yellow"/>
|
||||||
|
<TextBlock Text=" - " FontWeight="Bold" Foreground="White" Margin="5,0"/>
|
||||||
|
<TextBlock Text="{Binding Shortcuts.Count, StringFormat=\{0\} shortcuts}" Foreground="LightGray"/>
|
||||||
|
<TextBlock Text=" (Pinned)" FontWeight="Bold" Foreground="Orange" Margin="5,0,0,0"
|
||||||
|
Visibility="{Binding IsPinnedMode, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- DataGrid -->
|
||||||
|
<DataGrid x:Name="ShortcutsDataGrid" Grid.Row="1"
|
||||||
|
ItemsSource="{Binding Shortcuts}"
|
||||||
|
SelectedItem="{Binding SelectedShortcut}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
CanUserDeleteRows="False"
|
||||||
|
SelectionMode="Single"
|
||||||
|
GridLinesVisibility="Horizontal"
|
||||||
|
HeadersVisibility="Column"
|
||||||
|
AlternatingRowBackground="LightGray"
|
||||||
|
RowHeaderWidth="0"
|
||||||
|
CanUserSortColumns="False"
|
||||||
|
IsReadOnly="False"
|
||||||
|
MouseDoubleClick="ShortcutsDataGrid_MouseDoubleClick"
|
||||||
|
CellEditEnding="ShortcutsDataGrid_CellEditEnding"
|
||||||
|
RowEditEnding="ShortcutsDataGrid_RowEditEnding">
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="🗑️ Eliminar" Click="MenuItem_Delete_Click" />
|
||||||
|
<Separator />
|
||||||
|
<MenuItem Header="⌨️ Capturar Teclas" Click="MenuItem_CaptureKeys_Click" />
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridCheckBoxColumn x:Name="FavoriteColumn"
|
||||||
|
Header="⭐"
|
||||||
|
Binding="{Binding IsFavorite, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="40"
|
||||||
|
MinWidth="40"/>
|
||||||
|
<DataGridTextColumn x:Name="ShortcutColumn"
|
||||||
|
Header="Atajo"
|
||||||
|
Binding="{Binding Shortcut, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Width="140"
|
||||||
|
MinWidth="100"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<DataGridTemplateColumn x:Name="DescriptionColumn"
|
||||||
|
Header="Descripción"
|
||||||
|
Width="*"
|
||||||
|
MinWidth="80">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Description}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
MaxWidth="400"
|
||||||
|
Margin="5,2"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataGridTemplateColumn.CellEditingTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
AcceptsTab="True"
|
||||||
|
MinHeight="40"
|
||||||
|
MaxHeight="120"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="LightBlue"
|
||||||
|
Margin="1"
|
||||||
|
Padding="2"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Top"
|
||||||
|
HorizontalAlignment="Stretch"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellEditingTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.RowStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<Setter Property="MinHeight" Value="40"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsFavorite}" Value="True">
|
||||||
|
<Setter Property="Background" Value="LightYellow"/>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding IsModified}" Value="True">
|
||||||
|
<Setter Property="FontStyle" Value="Italic"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.RowStyle>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
|
@ -0,0 +1,268 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using ShortcutsHelper.Models;
|
||||||
|
using ShortcutsHelper.ViewModels;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private MainViewModel ViewModel => (MainViewModel)DataContext;
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.Closing += MainWindow_Closing;
|
||||||
|
this.Loaded += MainWindow_Loaded;
|
||||||
|
this.SizeChanged += MainWindow_SizeChanged;
|
||||||
|
this.LocationChanged += MainWindow_LocationChanged;
|
||||||
|
this.Deactivated += MainWindow_Deactivated;
|
||||||
|
this.Activated += MainWindow_Activated;
|
||||||
|
|
||||||
|
// Suscribirse al evento de cambio de visibilidad
|
||||||
|
ViewModel.VisibilityChanged += OnVisibilityChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
LoadWindowSettings();
|
||||||
|
this.Opacity = 1.0; // Inicializar con opacidad completa
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVisibilityChanged(object? sender, bool shouldBeVisible)
|
||||||
|
{
|
||||||
|
// Ejecutar en el hilo de UI
|
||||||
|
Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
if (shouldBeVisible)
|
||||||
|
{
|
||||||
|
// Mostrar ventana con opacidad completa
|
||||||
|
if (this.Visibility == Visibility.Hidden)
|
||||||
|
{
|
||||||
|
this.Show();
|
||||||
|
}
|
||||||
|
if (this.WindowState == WindowState.Minimized)
|
||||||
|
{
|
||||||
|
this.WindowState = WindowState.Normal;
|
||||||
|
}
|
||||||
|
this.Opacity = 1.0; // 100% opacidad
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// En lugar de ocultar, usar transparencia del 20%
|
||||||
|
this.Opacity = 0.2; // 20% opacidad
|
||||||
|
// Mantener la ventana visible pero semi-transparente
|
||||||
|
if (this.Visibility == Visibility.Hidden)
|
||||||
|
{
|
||||||
|
this.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PinButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.TogglePinMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadWindowSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = ViewModel.GetCurrentApplicationSettings();
|
||||||
|
|
||||||
|
this.Left = settings.Left;
|
||||||
|
this.Top = settings.Top;
|
||||||
|
this.Width = settings.Width;
|
||||||
|
this.Height = settings.Height;
|
||||||
|
|
||||||
|
// Aplicar anchos de columna
|
||||||
|
if (ShortcutsDataGrid != null)
|
||||||
|
{
|
||||||
|
FavoriteColumn.Width = settings.FavoriteColumnWidth;
|
||||||
|
ShortcutColumn.Width = settings.ShortcutColumnWidth;
|
||||||
|
// Para DataGridTemplateColumn necesitamos manejar el ancho diferente
|
||||||
|
DescriptionColumn.Width = new DataGridLength(settings.DescriptionColumnWidth, DataGridLengthUnitType.Pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al cargar configuración de ventana: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isInitialized)
|
||||||
|
{
|
||||||
|
SaveWindowSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_LocationChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_isInitialized)
|
||||||
|
{
|
||||||
|
SaveWindowSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveWindowSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ViewModel.UpdateWindowSettings(this.Left, this.Top, this.Width, this.Height);
|
||||||
|
|
||||||
|
if (ShortcutsDataGrid != null)
|
||||||
|
{
|
||||||
|
// Para DataGridTemplateColumn obtenemos el ancho diferente
|
||||||
|
double descriptionWidth = DescriptionColumn.ActualWidth;
|
||||||
|
|
||||||
|
ViewModel.UpdateColumnWidths(
|
||||||
|
FavoriteColumn.Width.Value,
|
||||||
|
ShortcutColumn.Width.Value,
|
||||||
|
descriptionWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al guardar configuración de ventana: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Deactivated(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Confirmar cualquier edición pendiente en el DataGrid
|
||||||
|
CommitDataGridEdits();
|
||||||
|
|
||||||
|
// Guardar cambios cuando pierde el foco
|
||||||
|
ViewModel.SavePendingChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Activated(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Cuando la ventana se activa, asegurar opacidad completa
|
||||||
|
this.Opacity = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Confirmar cualquier edición pendiente en el DataGrid
|
||||||
|
CommitDataGridEdits();
|
||||||
|
|
||||||
|
SaveWindowSettings();
|
||||||
|
|
||||||
|
// Desuscribirse del evento
|
||||||
|
ViewModel.VisibilityChanged -= OnVisibilityChanged;
|
||||||
|
|
||||||
|
ViewModel.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al cerrar aplicación: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_Delete_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.DeleteSelectedShortcut();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MenuItem_CaptureKeys_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.StartKeyCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShortcutsDataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
// Verificar si se hizo doble clic en una celda (no en el header)
|
||||||
|
if (e.OriginalSource is FrameworkElement element)
|
||||||
|
{
|
||||||
|
// Buscar si el clic fue en una celda del DataGrid
|
||||||
|
var cell = element.GetVisualParent<DataGridCell>();
|
||||||
|
if (cell != null && cell.Column == DescriptionColumn)
|
||||||
|
{
|
||||||
|
// Iniciar edición de la celda de descripción
|
||||||
|
ShortcutsDataGrid.BeginEdit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShortcutsDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
|
||||||
|
{
|
||||||
|
// Este evento se dispara cuando termina la edición de una celda
|
||||||
|
// No cancelamos la edición aquí, solo nos aseguramos de que se procese
|
||||||
|
if (!e.Cancel)
|
||||||
|
{
|
||||||
|
// Forzar la actualización del binding
|
||||||
|
Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
if (e.EditingElement is System.Windows.Controls.TextBox textBox)
|
||||||
|
{
|
||||||
|
var bindingExpression = textBox.GetBindingExpression(System.Windows.Controls.TextBox.TextProperty);
|
||||||
|
bindingExpression?.UpdateSource();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShortcutsDataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
|
||||||
|
{
|
||||||
|
// Este evento se dispara cuando termina la edición de una fila
|
||||||
|
// Aquí podrían guardarse los cambios si fuera necesario
|
||||||
|
if (!e.Cancel && e.Row.Item is ShortcutRecord shortcut)
|
||||||
|
{
|
||||||
|
// Forzar guardar cambios después de que termine la edición de la fila
|
||||||
|
Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
if (shortcut.IsModified)
|
||||||
|
{
|
||||||
|
ViewModel.SavePendingChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommitDataGridEdits()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ShortcutsDataGrid != null)
|
||||||
|
{
|
||||||
|
// Terminar cualquier edición activa
|
||||||
|
ShortcutsDataGrid.CommitEdit(DataGridEditingUnit.Row, true);
|
||||||
|
ShortcutsDataGrid.CommitEdit(DataGridEditingUnit.Cell, true);
|
||||||
|
|
||||||
|
// Actualizar el binding
|
||||||
|
ShortcutsDataGrid.UpdateLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al confirmar ediciones del DataGrid: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método de extensión para buscar elementos padre en el árbol visual
|
||||||
|
public static class VisualTreeHelperExtensions
|
||||||
|
{
|
||||||
|
public static T? GetVisualParent<T>(this DependencyObject child) where T : DependencyObject
|
||||||
|
{
|
||||||
|
var parent = VisualTreeHelper.GetParent(child);
|
||||||
|
if (parent == null) return null;
|
||||||
|
if (parent is T) return (T)parent;
|
||||||
|
return parent.GetVisualParent<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Models
|
||||||
|
{
|
||||||
|
public class AppConfiguration
|
||||||
|
{
|
||||||
|
public Dictionary<string, ApplicationSettings> Applications { get; set; } = new();
|
||||||
|
public string LastActiveApplication { get; set; } = "";
|
||||||
|
public int InactivityTimeoutMinutes { get; set; } = 5;
|
||||||
|
public bool IsPinnedMode { get; set; } = true;
|
||||||
|
public string PinnedApplication { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Models
|
||||||
|
{
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public Dictionary<string, ApplicationWindowSettings> ApplicationSettings { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ApplicationWindowSettings
|
||||||
|
{
|
||||||
|
public double Left { get; set; } = 100;
|
||||||
|
public double Top { get; set; } = 100;
|
||||||
|
public double Width { get; set; } = 400;
|
||||||
|
public double Height { get; set; } = 300;
|
||||||
|
public double FavoriteColumnWidth { get; set; } = 40;
|
||||||
|
public double ShortcutColumnWidth { get; set; } = 140;
|
||||||
|
public double DescriptionColumnWidth { get; set; } = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsManager
|
||||||
|
{
|
||||||
|
private static readonly string SettingsPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"ShortcutsHelper",
|
||||||
|
"app-settings.json");
|
||||||
|
|
||||||
|
private static AppSettings? _settings;
|
||||||
|
|
||||||
|
public static AppSettings LoadSettings()
|
||||||
|
{
|
||||||
|
if (_settings != null)
|
||||||
|
return _settings;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(SettingsPath))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(SettingsPath);
|
||||||
|
_settings = JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_settings = new AppSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_settings = new AppSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveSettings(AppSettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dir = Path.GetDirectoryName(SettingsPath);
|
||||||
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(SettingsPath, json);
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignorar errores al guardar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApplicationWindowSettings GetApplicationSettings(string applicationName)
|
||||||
|
{
|
||||||
|
var settings = LoadSettings();
|
||||||
|
|
||||||
|
if (!settings.ApplicationSettings.ContainsKey(applicationName))
|
||||||
|
{
|
||||||
|
// Configuración por defecto para aplicaciones nuevas
|
||||||
|
settings.ApplicationSettings[applicationName] = new ApplicationWindowSettings
|
||||||
|
{
|
||||||
|
Left = 100,
|
||||||
|
Top = 100,
|
||||||
|
Width = 400,
|
||||||
|
Height = 300,
|
||||||
|
FavoriteColumnWidth = 40,
|
||||||
|
ShortcutColumnWidth = 140,
|
||||||
|
DescriptionColumnWidth = 200
|
||||||
|
};
|
||||||
|
SaveSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings.ApplicationSettings[applicationName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveApplicationSettings(string applicationName, ApplicationWindowSettings appSettings)
|
||||||
|
{
|
||||||
|
var settings = LoadSettings();
|
||||||
|
settings.ApplicationSettings[applicationName] = appSettings;
|
||||||
|
SaveSettings(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace ShortcutsHelper.Models
|
||||||
|
{
|
||||||
|
public class ApplicationSettings
|
||||||
|
{
|
||||||
|
public double Left { get; set; } = 100;
|
||||||
|
public double Top { get; set; } = 100;
|
||||||
|
public double Width { get; set; } = 600;
|
||||||
|
public double Height { get; set; } = 400;
|
||||||
|
public double FavoriteColumnWidth { get; set; } = 40;
|
||||||
|
public double ShortcutColumnWidth { get; set; } = 150;
|
||||||
|
public double DescriptionColumnWidth { get; set; } = 350;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Models
|
||||||
|
{
|
||||||
|
public class ShortcutRecord : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private string _application = "";
|
||||||
|
private string _shortcut = "";
|
||||||
|
private string _description = "";
|
||||||
|
private bool _isFavorite = false;
|
||||||
|
private bool _isModified = false;
|
||||||
|
|
||||||
|
public string Application
|
||||||
|
{
|
||||||
|
get => _application;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_application != value)
|
||||||
|
{
|
||||||
|
_application = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Shortcut
|
||||||
|
{
|
||||||
|
get => _shortcut;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_shortcut != value)
|
||||||
|
{
|
||||||
|
_shortcut = value;
|
||||||
|
IsModified = true;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get => _description;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_description != value)
|
||||||
|
{
|
||||||
|
_description = value;
|
||||||
|
IsModified = true;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFavorite
|
||||||
|
{
|
||||||
|
get => _isFavorite;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isFavorite != value)
|
||||||
|
{
|
||||||
|
_isFavorite = value;
|
||||||
|
IsModified = true;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsModified
|
||||||
|
{
|
||||||
|
get => _isModified;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isModified != value)
|
||||||
|
{
|
||||||
|
_isModified = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using ShortcutsHelper.Models;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Services
|
||||||
|
{
|
||||||
|
public class ConfigurationService
|
||||||
|
{
|
||||||
|
private const string ConfigFileName = "shortcuts_config.json";
|
||||||
|
private static string ConfigFilePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ShortcutsHelper", ConfigFileName);
|
||||||
|
|
||||||
|
public AppConfiguration LoadConfiguration()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(ConfigFilePath))
|
||||||
|
{
|
||||||
|
string json = File.ReadAllText(ConfigFilePath);
|
||||||
|
return JsonSerializer.Deserialize<AppConfiguration>(json) ?? new AppConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al cargar configuración: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveConfiguration(AppConfiguration config)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string directory = Path.GetDirectoryName(ConfigFilePath)!;
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
|
string json = JsonSerializer.Serialize(config, options);
|
||||||
|
File.WriteAllText(ConfigFilePath, json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al guardar configuración: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationSettings GetApplicationSettings(AppConfiguration config, string applicationName)
|
||||||
|
{
|
||||||
|
if (config.Applications.TryGetValue(applicationName, out var settings))
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear configuración por defecto para nueva aplicación
|
||||||
|
var defaultSettings = new ApplicationSettings();
|
||||||
|
config.Applications[applicationName] = defaultSettings;
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateApplicationSettings(AppConfiguration config, string applicationName, ApplicationSettings settings)
|
||||||
|
{
|
||||||
|
config.Applications[applicationName] = settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Gma.System.MouseKeyHook;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Services
|
||||||
|
{
|
||||||
|
public class KeyCaptureService : IDisposable
|
||||||
|
{
|
||||||
|
private IKeyboardMouseEvents? _globalHook;
|
||||||
|
private bool _isCapturing = false;
|
||||||
|
private List<string> _capturedKeys = new();
|
||||||
|
|
||||||
|
public event EventHandler<string>? KeyCaptured;
|
||||||
|
public event EventHandler? CaptureCancelled;
|
||||||
|
|
||||||
|
public bool IsCapturing => _isCapturing;
|
||||||
|
|
||||||
|
public void StartCapturing()
|
||||||
|
{
|
||||||
|
if (_isCapturing) return;
|
||||||
|
|
||||||
|
_isCapturing = true;
|
||||||
|
_capturedKeys.Clear();
|
||||||
|
|
||||||
|
_globalHook = Hook.GlobalEvents();
|
||||||
|
_globalHook.KeyDown += OnKeyDown;
|
||||||
|
_globalHook.KeyUp += OnKeyUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopCapturing()
|
||||||
|
{
|
||||||
|
_isCapturing = false;
|
||||||
|
if (_globalHook != null)
|
||||||
|
{
|
||||||
|
_globalHook.KeyDown -= OnKeyDown;
|
||||||
|
_globalHook.KeyUp -= OnKeyUp;
|
||||||
|
_globalHook.Dispose();
|
||||||
|
_globalHook = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isCapturing) return;
|
||||||
|
|
||||||
|
if (e.KeyCode == Keys.Escape)
|
||||||
|
{
|
||||||
|
StopCapturing();
|
||||||
|
CaptureCancelled?.Invoke(this, EventArgs.Empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyString = BuildKeyString(e);
|
||||||
|
if (!string.IsNullOrEmpty(keyString))
|
||||||
|
{
|
||||||
|
_capturedKeys.Clear();
|
||||||
|
_capturedKeys.Add(keyString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyUp(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isCapturing || _capturedKeys.Count == 0) return;
|
||||||
|
|
||||||
|
StopCapturing();
|
||||||
|
KeyCaptured?.Invoke(this, _capturedKeys.First().Replace(" ", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildKeyString(KeyEventArgs e)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
|
||||||
|
if ((Control.ModifierKeys & Keys.Control) != 0) parts.Add("Ctrl");
|
||||||
|
if ((Control.ModifierKeys & Keys.Alt) != 0) parts.Add("Alt");
|
||||||
|
if ((Control.ModifierKeys & Keys.Shift) != 0) parts.Add("Shift");
|
||||||
|
if ((Control.ModifierKeys & Keys.LWin) != 0 || (Control.ModifierKeys & Keys.RWin) != 0) parts.Add("Win");
|
||||||
|
|
||||||
|
if (e.KeyCode != Keys.ControlKey && e.KeyCode != Keys.LControlKey && e.KeyCode != Keys.RControlKey &&
|
||||||
|
e.KeyCode != Keys.Menu && e.KeyCode != Keys.LMenu && e.KeyCode != Keys.RMenu &&
|
||||||
|
e.KeyCode != Keys.ShiftKey && e.KeyCode != Keys.LShiftKey && e.KeyCode != Keys.RShiftKey &&
|
||||||
|
e.KeyCode != Keys.LWin && e.KeyCode != Keys.RWin)
|
||||||
|
{
|
||||||
|
parts.Add(GetKeyName(e.KeyCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("+", parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetKeyName(Keys key)
|
||||||
|
{
|
||||||
|
return key switch
|
||||||
|
{
|
||||||
|
Keys.Space => "Space",
|
||||||
|
Keys.Enter => "Enter",
|
||||||
|
Keys.Tab => "Tab",
|
||||||
|
Keys.Back => "Backspace",
|
||||||
|
Keys.Delete => "Delete",
|
||||||
|
Keys.Insert => "Insert",
|
||||||
|
Keys.Home => "Home",
|
||||||
|
Keys.End => "End",
|
||||||
|
Keys.PageUp => "PageUp",
|
||||||
|
Keys.PageDown => "PageDown",
|
||||||
|
Keys.Up => "↑",
|
||||||
|
Keys.Down => "↓",
|
||||||
|
Keys.Left => "←",
|
||||||
|
Keys.Right => "→",
|
||||||
|
Keys.F1 => "F1",
|
||||||
|
Keys.F2 => "F2",
|
||||||
|
Keys.F3 => "F3",
|
||||||
|
Keys.F4 => "F4",
|
||||||
|
Keys.F5 => "F5",
|
||||||
|
Keys.F6 => "F6",
|
||||||
|
Keys.F7 => "F7",
|
||||||
|
Keys.F8 => "F8",
|
||||||
|
Keys.F9 => "F9",
|
||||||
|
Keys.F10 => "F10",
|
||||||
|
Keys.F11 => "F11",
|
||||||
|
Keys.F12 => "F12",
|
||||||
|
_ => key.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StopCapturing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using libObsidean;
|
||||||
|
using ShortcutsHelper.Models;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.Services
|
||||||
|
{
|
||||||
|
public class ShortcutService
|
||||||
|
{
|
||||||
|
private readonly Obsidean _obsidean = new();
|
||||||
|
private readonly Dictionary<string, List<ShortcutRecord>> _shortcutsByApplication = new();
|
||||||
|
private bool _isLoaded = false;
|
||||||
|
|
||||||
|
public event EventHandler<string>? ApplicationShortcutsChanged;
|
||||||
|
|
||||||
|
public void LoadAllShortcuts()
|
||||||
|
{
|
||||||
|
if (_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tabla = _obsidean.LeerShortcuts();
|
||||||
|
if (tabla != null && tabla.GetLength(0) > 1)
|
||||||
|
{
|
||||||
|
_shortcutsByApplication.Clear();
|
||||||
|
|
||||||
|
for (int i = 1; i < tabla.GetLength(0); i++)
|
||||||
|
{
|
||||||
|
string app = tabla[i, 0] ?? "";
|
||||||
|
string shortcut = tabla[i, 1] ?? "";
|
||||||
|
string description = tabla[i, 2] ?? "";
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(app) && (!string.IsNullOrWhiteSpace(shortcut) || !string.IsNullOrWhiteSpace(description)))
|
||||||
|
{
|
||||||
|
bool isFavorite = false;
|
||||||
|
if (tabla.GetLength(1) > 3 && !string.IsNullOrWhiteSpace(tabla[i, 3]))
|
||||||
|
{
|
||||||
|
bool.TryParse(tabla[i, 3], out isFavorite);
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = new ShortcutRecord
|
||||||
|
{
|
||||||
|
Application = app,
|
||||||
|
Shortcut = shortcut,
|
||||||
|
Description = description,
|
||||||
|
IsFavorite = isFavorite,
|
||||||
|
IsModified = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_shortcutsByApplication.ContainsKey(app))
|
||||||
|
{
|
||||||
|
_shortcutsByApplication[app] = new List<ShortcutRecord>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_shortcutsByApplication[app].Add(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al cargar shortcuts: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ShortcutRecord> GetShortcutsForApplication(string applicationName)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) LoadAllShortcuts();
|
||||||
|
|
||||||
|
if (_shortcutsByApplication.TryGetValue(applicationName, out var shortcuts))
|
||||||
|
{
|
||||||
|
return shortcuts.OrderByDescending(s => s.IsFavorite).ThenBy(s => s.Shortcut).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<ShortcutRecord>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateShortcut(ShortcutRecord shortcut)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(shortcut.Application)) return;
|
||||||
|
|
||||||
|
if (!_shortcutsByApplication.ContainsKey(shortcut.Application))
|
||||||
|
{
|
||||||
|
_shortcutsByApplication[shortcut.Application] = new List<ShortcutRecord>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = _shortcutsByApplication[shortcut.Application]
|
||||||
|
.FirstOrDefault(s => s.Shortcut == shortcut.Shortcut);
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Description = shortcut.Description;
|
||||||
|
existing.IsFavorite = shortcut.IsFavorite;
|
||||||
|
// No marcar como IsModified = false aquí, dejar que SaveAllShortcuts() lo haga
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No marcar como IsModified = false aquí, dejar que SaveAllShortcuts() lo haga
|
||||||
|
_shortcutsByApplication[shortcut.Application].Add(shortcut);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationShortcutsChanged?.Invoke(this, shortcut.Application);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveShortcut(string applicationName, string shortcut)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(applicationName) || string.IsNullOrWhiteSpace(shortcut)) return;
|
||||||
|
|
||||||
|
if (_shortcutsByApplication.TryGetValue(applicationName, out var shortcuts))
|
||||||
|
{
|
||||||
|
var toRemove = shortcuts.FirstOrDefault(s => s.Shortcut == shortcut);
|
||||||
|
if (toRemove != null)
|
||||||
|
{
|
||||||
|
shortcuts.Remove(toRemove);
|
||||||
|
ApplicationShortcutsChanged?.Invoke(this, applicationName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveAllShortcuts()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allShortcuts = new List<ShortcutRecord>();
|
||||||
|
foreach (var appShortcuts in _shortcutsByApplication.Values)
|
||||||
|
{
|
||||||
|
allShortcuts.AddRange(appShortcuts.Where(s => !string.IsNullOrWhiteSpace(s.Shortcut)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allShortcuts.Count == 0) return;
|
||||||
|
|
||||||
|
// Crear tabla para guardar
|
||||||
|
var tabla = new string[allShortcuts.Count + 1, 4];
|
||||||
|
tabla[0, 0] = "Application";
|
||||||
|
tabla[0, 1] = "Shortcut";
|
||||||
|
tabla[0, 2] = "Description";
|
||||||
|
tabla[0, 3] = "IsFavorite";
|
||||||
|
|
||||||
|
for (int i = 0; i < allShortcuts.Count; i++)
|
||||||
|
{
|
||||||
|
var shortcut = allShortcuts[i];
|
||||||
|
tabla[i + 1, 0] = shortcut.Application;
|
||||||
|
tabla[i + 1, 1] = shortcut.Shortcut;
|
||||||
|
tabla[i + 1, 2] = shortcut.Description;
|
||||||
|
tabla[i + 1, 3] = shortcut.IsFavorite.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar en Obsidian
|
||||||
|
var vaultPath = GetVaultPath("VM");
|
||||||
|
if (!string.IsNullOrEmpty(vaultPath))
|
||||||
|
{
|
||||||
|
string pathToMarkdown = Path.Combine(vaultPath, "DB", "Shortcuts", "Shortcuts.md");
|
||||||
|
libObsidean.Obsidean.SaveTableToMarkdown(pathToMarkdown, tabla);
|
||||||
|
|
||||||
|
// Marcar todos como no modificados
|
||||||
|
foreach (var appShortcuts in _shortcutsByApplication.Values)
|
||||||
|
{
|
||||||
|
foreach (var shortcut in appShortcuts)
|
||||||
|
{
|
||||||
|
shortcut.IsModified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al guardar shortcuts: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasModifiedShortcuts()
|
||||||
|
{
|
||||||
|
return _shortcutsByApplication.Values
|
||||||
|
.Any(shortcuts => shortcuts.Any(s => s.IsModified));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetAllApplicationNames()
|
||||||
|
{
|
||||||
|
if (!_isLoaded) LoadAllShortcuts();
|
||||||
|
return _shortcutsByApplication.Keys.OrderBy(k => k).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetVaultPath(string vaultName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
string pathToJsonFile = Path.Combine(appDataPath, "obsidian", "obsidian.json");
|
||||||
|
if (File.Exists(pathToJsonFile))
|
||||||
|
{
|
||||||
|
string jsonContent = File.ReadAllText(pathToJsonFile);
|
||||||
|
var jsonObject = JObject.Parse(jsonContent);
|
||||||
|
var vaults = jsonObject["vaults"] as JObject;
|
||||||
|
|
||||||
|
if (vaults != null)
|
||||||
|
{
|
||||||
|
foreach (var vault in vaults)
|
||||||
|
{
|
||||||
|
var pathToken = vault.Value?["path"];
|
||||||
|
if (pathToken != null)
|
||||||
|
{
|
||||||
|
string? path = pathToken.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
string? dirPath = Path.GetDirectoryName(path.TrimEnd('\\') + "\\");
|
||||||
|
if (!string.IsNullOrEmpty(dirPath))
|
||||||
|
{
|
||||||
|
string lastDirectoryName = Path.GetFileName(dirPath);
|
||||||
|
if (lastDirectoryName.Equals(vaultName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error al leer vault: " + ex.Message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../Libraries/libObsidean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"dotnet.defaultSolution": "ShortcutsHelper/ShortcutsHelper.sln"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||||
|
<PackageReference Include="MouseKeyHook" Version="5.7.1" />
|
||||||
|
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.6.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Libraries\libObsidean\libObsidean.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="C:\Trabajo\Graphics\Icons\close.png" Link="Icons\close.png" />
|
||||||
|
<Resource Include="C:\Trabajo\Graphics\Icons\delete.png" Link="Icons\delete.png" />
|
||||||
|
<Resource Include="C:\Trabajo\Graphics\Icons\add-button.png" Link="Icons\add-button.png" />
|
||||||
|
<Resource Include="C:\Trabajo\Graphics\Icons\remove.png" Link="Icons\remove.png" />
|
||||||
|
<Resource Include="C:\Trabajo\Graphics\Icons\use.png" Link="Icons\use.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Icons\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutsHelper", "ShortcutsHelper.csproj", "{B3511550-5621-491D-A554-412664C38E4B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{B3511550-5621-491D-A554-412664C38E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B3511550-5621-491D-A554-412664C38E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B3511550-5621-491D-A554-412664C38E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B3511550-5621-491D-A554-412664C38E4B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {17860D8C-E283-4288-89AA-CC11A43770EC}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,412 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ShortcutsHelper.Models;
|
||||||
|
using ShortcutsHelper.Services;
|
||||||
|
using WpfApplication = System.Windows.Application;
|
||||||
|
using SystemTimer = System.Timers.Timer;
|
||||||
|
|
||||||
|
namespace ShortcutsHelper.ViewModels
|
||||||
|
{
|
||||||
|
public class MainViewModel : INotifyPropertyChanged, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ShortcutService _shortcutService;
|
||||||
|
private readonly ConfigurationService _configService;
|
||||||
|
private readonly KeyCaptureService _keyCaptureService;
|
||||||
|
private readonly SystemTimer _appDetectionTimer;
|
||||||
|
private readonly SystemTimer _inactivityTimer;
|
||||||
|
|
||||||
|
private string _currentApplication = "";
|
||||||
|
private ShortcutRecord? _selectedShortcut;
|
||||||
|
private string _originalDescription = "";
|
||||||
|
private bool _isInitialized = false;
|
||||||
|
private bool _isPinnedMode = true; // Por defecto está activo
|
||||||
|
|
||||||
|
public ObservableCollection<ShortcutRecord> Shortcuts { get; } = new();
|
||||||
|
|
||||||
|
public string CurrentApplication
|
||||||
|
{
|
||||||
|
get => _currentApplication;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_currentApplication != value)
|
||||||
|
{
|
||||||
|
_currentApplication = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
LoadShortcutsForCurrentApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShortcutRecord? SelectedShortcut
|
||||||
|
{
|
||||||
|
get => _selectedShortcut;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_selectedShortcut != value)
|
||||||
|
{
|
||||||
|
_selectedShortcut = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
EnsureEmptyRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCapturingKeys => _keyCaptureService.IsCapturing;
|
||||||
|
|
||||||
|
public bool IsPinnedMode
|
||||||
|
{
|
||||||
|
get => _isPinnedMode;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isPinnedMode != value)
|
||||||
|
{
|
||||||
|
_isPinnedMode = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
|
||||||
|
// Actualizar configuración
|
||||||
|
_appConfig.IsPinnedMode = value;
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
// Al activar Pin, guardar la aplicación actual como pinned (si no está vacía)
|
||||||
|
if (!string.IsNullOrEmpty(CurrentApplication))
|
||||||
|
{
|
||||||
|
_appConfig.PinnedApplication = CurrentApplication;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Al desactivar Pin, limpiar la aplicación pinned y mostrar la ventana
|
||||||
|
_appConfig.PinnedApplication = "";
|
||||||
|
VisibilityChanged?.Invoke(this, true); // Mostrar ventana cuando se desactiva Pin
|
||||||
|
}
|
||||||
|
|
||||||
|
_configService.SaveConfiguration(_appConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuración de aplicación actual
|
||||||
|
private AppConfiguration _appConfig = new();
|
||||||
|
private ApplicationSettings? _currentAppSettings;
|
||||||
|
|
||||||
|
// Evento para notificar cambios de visibilidad de la ventana
|
||||||
|
public event EventHandler<bool>? VisibilityChanged;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
public MainViewModel()
|
||||||
|
{
|
||||||
|
_shortcutService = new ShortcutService();
|
||||||
|
_configService = new ConfigurationService();
|
||||||
|
_keyCaptureService = new KeyCaptureService();
|
||||||
|
|
||||||
|
// Configurar timers
|
||||||
|
_appDetectionTimer = new SystemTimer(1000); // Cada segundo
|
||||||
|
_appDetectionTimer.Elapsed += OnAppDetectionTimer;
|
||||||
|
|
||||||
|
_inactivityTimer = new SystemTimer(TimeSpan.FromMinutes(5).TotalMilliseconds); // 5 minutos por defecto
|
||||||
|
_inactivityTimer.Elapsed += OnInactivityTimer;
|
||||||
|
|
||||||
|
// Suscribirse a eventos
|
||||||
|
_keyCaptureService.KeyCaptured += OnKeyCaptured;
|
||||||
|
_keyCaptureService.CaptureCancelled += OnCaptureCancelled;
|
||||||
|
_shortcutService.ApplicationShortcutsChanged += OnApplicationShortcutsChanged;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
|
// Cargar configuración
|
||||||
|
_appConfig = _configService.LoadConfiguration();
|
||||||
|
_inactivityTimer.Interval = TimeSpan.FromMinutes(_appConfig.InactivityTimeoutMinutes).TotalMilliseconds;
|
||||||
|
|
||||||
|
// Restaurar estado de Pin
|
||||||
|
_isPinnedMode = _appConfig.IsPinnedMode;
|
||||||
|
OnPropertyChanged(nameof(IsPinnedMode));
|
||||||
|
|
||||||
|
// Cargar todos los shortcuts de una vez
|
||||||
|
_shortcutService.LoadAllShortcuts();
|
||||||
|
|
||||||
|
// Si está en modo Pin y no hay aplicación pinned, establecer la primera aplicación detectada
|
||||||
|
// Pero primero detectar la aplicación actual para establecer CurrentApplication
|
||||||
|
string initialApp = GetActiveProcessName();
|
||||||
|
if (!string.IsNullOrWhiteSpace(initialApp) &&
|
||||||
|
!initialApp.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
CurrentApplication = initialApp;
|
||||||
|
LoadApplicationSettings();
|
||||||
|
|
||||||
|
// Si está en modo Pin y no hay aplicación pinned, establecer esta como pinned
|
||||||
|
if (IsPinnedMode && string.IsNullOrEmpty(_appConfig.PinnedApplication))
|
||||||
|
{
|
||||||
|
_appConfig.PinnedApplication = initialApp;
|
||||||
|
_configService.SaveConfiguration(_appConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar detección de aplicación
|
||||||
|
_appDetectionTimer.Start();
|
||||||
|
|
||||||
|
// Iniciar timer de inactividad
|
||||||
|
_inactivityTimer.Start();
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAppDetectionTimer(object? sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
DetectActiveApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInactivityTimer(object? sender, System.Timers.ElapsedEventArgs e)
|
||||||
|
{
|
||||||
|
// Guardar cambios por inactividad
|
||||||
|
SavePendingChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetectActiveApplication()
|
||||||
|
{
|
||||||
|
string appName = GetActiveProcessName();
|
||||||
|
if (string.IsNullOrWhiteSpace(appName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Si está en modo Pin
|
||||||
|
if (IsPinnedMode)
|
||||||
|
{
|
||||||
|
// Si no hay aplicación pinned, establecer la aplicación actual (excepto ShortcutsHelper)
|
||||||
|
if (string.IsNullOrEmpty(_appConfig.PinnedApplication) &&
|
||||||
|
!appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_appConfig.PinnedApplication = appName;
|
||||||
|
CurrentApplication = appName;
|
||||||
|
LoadApplicationSettings();
|
||||||
|
_configService.SaveConfiguration(_appConfig);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si hay aplicación pinned, manejar visibilidad
|
||||||
|
if (!string.IsNullOrEmpty(_appConfig.PinnedApplication))
|
||||||
|
{
|
||||||
|
bool isShortcutsHelper = appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase);
|
||||||
|
bool isPinnedApp = appName.Equals(_appConfig.PinnedApplication, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// Mostrar ventana solo si está en ShortcutsHelper o en la aplicación pinned
|
||||||
|
bool shouldBeVisible = isShortcutsHelper || isPinnedApp;
|
||||||
|
VisibilityChanged?.Invoke(this, shouldBeVisible);
|
||||||
|
|
||||||
|
// No cambiar la aplicación actual cuando está pinned
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modo normal: cambiar aplicación pero no mostrar ShortcutsHelper
|
||||||
|
if (appName.Equals("ShortcutsHelper", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (appName != CurrentApplication)
|
||||||
|
{
|
||||||
|
SaveCurrentApplicationSettings();
|
||||||
|
CurrentApplication = appName;
|
||||||
|
LoadApplicationSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TogglePinMode()
|
||||||
|
{
|
||||||
|
IsPinnedMode = !IsPinnedMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadShortcutsForCurrentApp()
|
||||||
|
{
|
||||||
|
WpfApplication.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Shortcuts.Clear();
|
||||||
|
var shortcuts = _shortcutService.GetShortcutsForApplication(CurrentApplication);
|
||||||
|
|
||||||
|
foreach (var shortcut in shortcuts)
|
||||||
|
{
|
||||||
|
Shortcuts.Add(shortcut);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureEmptyRows();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureEmptyRows()
|
||||||
|
{
|
||||||
|
WpfApplication.Current.Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
var emptyRows = Shortcuts.Where(s => string.IsNullOrWhiteSpace(s.Shortcut) && string.IsNullOrWhiteSpace(s.Description)).Count();
|
||||||
|
while (emptyRows < 3)
|
||||||
|
{
|
||||||
|
Shortcuts.Add(new ShortcutRecord { Application = CurrentApplication });
|
||||||
|
emptyRows++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationShortcutsChanged(object? sender, string applicationName)
|
||||||
|
{
|
||||||
|
if (applicationName == CurrentApplication)
|
||||||
|
{
|
||||||
|
LoadShortcutsForCurrentApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteSelectedShortcut()
|
||||||
|
{
|
||||||
|
if (SelectedShortcut != null && !string.IsNullOrWhiteSpace(SelectedShortcut.Shortcut))
|
||||||
|
{
|
||||||
|
_shortcutService.RemoveShortcut(CurrentApplication, SelectedShortcut.Shortcut);
|
||||||
|
Shortcuts.Remove(SelectedShortcut);
|
||||||
|
EnsureEmptyRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartKeyCapture()
|
||||||
|
{
|
||||||
|
if (SelectedShortcut == null) return;
|
||||||
|
|
||||||
|
if (_keyCaptureService.IsCapturing)
|
||||||
|
{
|
||||||
|
_keyCaptureService.StopCapturing();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_originalDescription = SelectedShortcut.Description;
|
||||||
|
SelectedShortcut.Description = "Presiona las teclas... (Esc para cancelar)";
|
||||||
|
_keyCaptureService.StartCapturing();
|
||||||
|
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyCaptured(object? sender, string keyString)
|
||||||
|
{
|
||||||
|
if (SelectedShortcut != null)
|
||||||
|
{
|
||||||
|
SelectedShortcut.Shortcut = keyString;
|
||||||
|
SelectedShortcut.Description = _originalDescription;
|
||||||
|
SelectedShortcut.IsModified = true;
|
||||||
|
}
|
||||||
|
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCaptureCancelled(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedShortcut != null)
|
||||||
|
{
|
||||||
|
SelectedShortcut.Description = _originalDescription;
|
||||||
|
}
|
||||||
|
OnPropertyChanged(nameof(IsCapturingKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SavePendingChanges()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Guardar shortcuts modificados
|
||||||
|
var modifiedShortcuts = Shortcuts.Where(s => s.IsModified && !string.IsNullOrWhiteSpace(s.Shortcut)).ToList();
|
||||||
|
foreach (var shortcut in modifiedShortcuts)
|
||||||
|
{
|
||||||
|
_shortcutService.AddOrUpdateShortcut(shortcut);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_shortcutService.HasModifiedShortcuts())
|
||||||
|
{
|
||||||
|
_shortcutService.SaveAllShortcuts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error al guardar cambios: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationSettings GetCurrentApplicationSettings()
|
||||||
|
{
|
||||||
|
return _configService.GetApplicationSettings(_appConfig, CurrentApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveCurrentApplicationSettings()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(CurrentApplication) || _currentAppSettings == null) return;
|
||||||
|
|
||||||
|
_configService.UpdateApplicationSettings(_appConfig, CurrentApplication, _currentAppSettings);
|
||||||
|
_configService.SaveConfiguration(_appConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadApplicationSettings()
|
||||||
|
{
|
||||||
|
_currentAppSettings = _configService.GetApplicationSettings(_appConfig, CurrentApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateWindowSettings(double left, double top, double width, double height)
|
||||||
|
{
|
||||||
|
if (_currentAppSettings != null)
|
||||||
|
{
|
||||||
|
_currentAppSettings.Left = left;
|
||||||
|
_currentAppSettings.Top = top;
|
||||||
|
_currentAppSettings.Width = width;
|
||||||
|
_currentAppSettings.Height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateColumnWidths(double favoriteWidth, double shortcutWidth, double descriptionWidth)
|
||||||
|
{
|
||||||
|
if (_currentAppSettings != null)
|
||||||
|
{
|
||||||
|
_currentAppSettings.FavoriteColumnWidth = favoriteWidth;
|
||||||
|
_currentAppSettings.ShortcutColumnWidth = shortcutWidth;
|
||||||
|
_currentAppSettings.DescriptionColumnWidth = descriptionWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetActiveProcessName()
|
||||||
|
{
|
||||||
|
IntPtr hWnd = GetForegroundWindow();
|
||||||
|
if (hWnd == IntPtr.Zero) return string.Empty;
|
||||||
|
|
||||||
|
GetWindowThreadProcessId(hWnd, out uint pid);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var proc = Process.GetProcessById((int)pid);
|
||||||
|
return proc.ProcessName;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
SavePendingChanges();
|
||||||
|
SaveCurrentApplicationSettings();
|
||||||
|
|
||||||
|
_appDetectionTimer?.Stop();
|
||||||
|
_appDetectionTimer?.Dispose();
|
||||||
|
_inactivityTimer?.Stop();
|
||||||
|
_inactivityTimer?.Dispose();
|
||||||
|
_keyCaptureService?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetForegroundWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue