Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/Presentation/QBD.WPF/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
<controls:ViewModelTemplateSelector x:Key="ViewModelTemplateSelector"/>
</Window.Resources>

<Window.InputBindings>
<KeyBinding Key="F5" Command="{Binding HomePageCommand}"/>
<KeyBinding Modifiers="Control" Key="F" Command="{Binding FocusSearchCommand}"/>
<KeyBinding Key="Escape" Command="{Binding CloseCurrentTabCommand}"/>
<KeyBinding Modifiers="Control" Key="S" Command="{Binding SaveCommand}"/>
<KeyBinding Modifiers="Control" Key="N" Command="{Binding NewCommand}"/>
<KeyBinding Key="F1" Command="{Binding ShowKeyboardShortcutsCommand}"/>
</Window.InputBindings>

<DockPanel LastChildFill="True">
<!-- Menu Bar -->
<Menu DockPanel.Dock="Top"
Expand All @@ -30,9 +39,11 @@
<MenuItem Header="Cut" InputGestureText="Ctrl+X"/>
<MenuItem Header="Copy" InputGestureText="Ctrl+C"/>
<MenuItem Header="Paste" InputGestureText="Ctrl+V"/>
<Separator/>
<MenuItem Header="Find" InputGestureText="Ctrl+F"/>
</MenuItem>
<MenuItem Header="_View">
<MenuItem Header="Home Page" Click="HomePage_Click"/>
<MenuItem Header="Home Page" Click="HomePage_Click" InputGestureText="F5"/>
<Separator/>
<MenuItem Header="Toggle Dark Mode" Click="ToggleDarkMode_Click"/>
<Separator/>
Expand Down Expand Up @@ -118,8 +129,11 @@
</MenuItem>
<MenuItem Header="_Window">
<MenuItem Header="Close All" Click="CloseAllTabs_Click"/>
<MenuItem Header="Close Tab" InputGestureText="Esc"/>
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="Keyboard Shortcuts" Click="KeyboardShortcuts_Click" InputGestureText="F1"/>
<Separator/>
<MenuItem Header="About QuickBooks Desktop" Click="About_Click"/>
</MenuItem>
</Menu>
Expand Down
275 changes: 245 additions & 30 deletions src/Presentation/QBD.WPF/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using QBD.Application.Interfaces;
using QBD.Application.ViewModels;

Expand All @@ -13,18 +15,32 @@ public partial class MainWindow : Window
private bool _isDarkMode = false;
private string _themeConfigFile = string.Empty;

public ICommand HomePageCommand { get; }
public ICommand FocusSearchCommand { get; }
public ICommand CloseCurrentTabCommand { get; }
public ICommand SaveCommand { get; }
public ICommand NewCommand { get; }
public ICommand ShowKeyboardShortcutsCommand { get; }

public MainWindow(INavigationService navigationService, HomePageViewModel homePageViewModel)
{
InitializeComponent();
_navigationService = navigationService;

HomePageCommand = new ActionCommand(() => _navigationService.OpenHomePage());
FocusSearchCommand = new ActionCommand(() => FocusSearch());
CloseCurrentTabCommand = new ActionCommand(() => CloseCurrentTab());
SaveCommand = new ActionCommand(() => ExecuteGlobalSave());
NewCommand = new ActionCommand(() => ExecuteGlobalNew());
ShowKeyboardShortcutsCommand = new ActionCommand(() => ShowKeyboardShortcuts());

InitializeComponent();

this.DataContext = this;

try
{
var appDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "QBD", "QBD.WPF");
if (!Directory.Exists(appDataDirectory))
{
Directory.CreateDirectory(appDataDirectory);
}
if (!Directory.Exists(appDataDirectory)) Directory.CreateDirectory(appDataDirectory);

_themeConfigFile = Path.Combine(appDataDirectory, "theme.cfg");

Expand All @@ -51,8 +67,7 @@ public void OpenTab(ViewModelBase viewModel)
{
foreach (var item in WorkspaceTabs.Items)
{
if (item is ViewModelBase existing && existing.GetType() == viewModel.GetType()
&& existing.Title == viewModel.Title)
if (item is ViewModelBase existing && existing.GetType() == viewModel.GetType() && existing.Title == viewModel.Title)
{
WorkspaceTabs.SelectedItem = item;
return;
Expand All @@ -62,15 +77,8 @@ public void OpenTab(ViewModelBase viewModel)
WorkspaceTabs.SelectedItem = viewModel;
}

public void CloseTab(object viewModel)
{
WorkspaceTabs.Items.Remove(viewModel);
}

public void CloseAllTabs()
{
WorkspaceTabs.Items.Clear();
}
public void CloseTab(object viewModel) => WorkspaceTabs.Items.Remove(viewModel);
public void CloseAllTabs() => WorkspaceTabs.Items.Clear();

private void Exit_Click(object sender, RoutedEventArgs e) => System.Windows.Application.Current.Shutdown();
private void HomePage_Click(object sender, RoutedEventArgs e) => _navigationService.OpenHomePage();
Expand Down Expand Up @@ -116,36 +124,243 @@ public void CloseAllTabs()

private void CloseTab_Click(object sender, RoutedEventArgs e)
{
if (sender is Button btn && btn.DataContext is ViewModelBase vm)
{
CloseTab(vm);
}
if (sender is Button btn && btn.DataContext is ViewModelBase vm) CloseTab(vm);
}

private void CloseAllTabs_Click(object sender, RoutedEventArgs e) => CloseAllTabs();

private void KeyboardShortcuts_Click(object sender, RoutedEventArgs e) => ShowKeyboardShortcuts();

private void ShowKeyboardShortcuts()
{
var shortcuts = new Window
{
Title = "Keyboard Shortcuts",
Width = 450,
Height = 480,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
Owner = this,
ResizeMode = ResizeMode.NoResize,
Background = (System.Windows.Media.Brush)FindResource("ThemeWindowBackground")
};

var grid = new Grid { Margin = new Thickness(20) };
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

var header = new TextBlock
{
Text = "Keyboard Shortcuts",
FontSize = 18,
FontWeight = FontWeights.Bold,
Margin = new Thickness(0, 0, 0, 12),
Foreground = (System.Windows.Media.Brush)FindResource("ThemeForeground")
};
Grid.SetRow(header, 0);

var items = new[]
{
("F1", "Show this help dialog"),
("F5", "Go to Home Page"),
("Ctrl+N", "New transaction (context-aware)"),
("Ctrl+S", "Save current form"),
("Ctrl+F", "Focus search box"),
("Ctrl+A", "Chart of Accounts"),
("Esc", "Close current tab"),
("Tab", "Move to next field"),
("Shift+Tab", "Move to previous field"),
("Alt+S", "Save (in forms)"),
("Alt+P", "Save & Post (in forms)"),
("Alt+L", "Clear (in forms)"),
("Alt+V", "Void (in forms)"),
("Alt+R", "Print (in forms)"),
("Alt+N", "New (in lists)"),
("Alt+E", "Edit (in lists)"),
("Alt+D", "Delete (in lists)"),
};

var listView = new ListView
{
BorderThickness = new Thickness(1),
BorderBrush = (System.Windows.Media.Brush)FindResource("ThemeBorderBrush"),
Background = (System.Windows.Media.Brush)FindResource("ThemeControlBackground")
};

var gridView = new GridView();
gridView.Columns.Add(new GridViewColumn
{
Header = "Shortcut",
Width = 120,
DisplayMemberBinding = new System.Windows.Data.Binding("Item1")
});
gridView.Columns.Add(new GridViewColumn
{
Header = "Action",
Width = 280,
DisplayMemberBinding = new System.Windows.Data.Binding("Item2")
});
listView.View = gridView;

foreach (var item in items)
listView.Items.Add(item);

Grid.SetRow(listView, 1);

var closeBtn = new Button
{
Content = "Close",
Padding = new Thickness(20, 6, 20, 6),
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(0, 12, 0, 0)
};
closeBtn.Click += (_, _) => shortcuts.Close();
Grid.SetRow(closeBtn, 2);

grid.Children.Add(header);
grid.Children.Add(listView);
grid.Children.Add(closeBtn);
shortcuts.Content = grid;
shortcuts.ShowDialog();
}

private void About_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("QuickBooks Desktop Enterprise Clone\nVersion 1.0\n\nA full-featured accounting application.",
"About", MessageBoxButton.OK, MessageBoxImage.Information);
MessageBox.Show("QuickBooks Desktop Enterprise Clone\nVersion 1.0\n\nA full-featured accounting application.", "About", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void ToggleDarkMode_Click(object sender, RoutedEventArgs e)
{
_isDarkMode = !_isDarkMode;
var app = (App)System.Windows.Application.Current;
app.ChangeTheme(_isDarkMode);
((App)System.Windows.Application.Current).ChangeTheme(_isDarkMode);
try { if (!string.IsNullOrEmpty(_themeConfigFile)) File.WriteAllText(_themeConfigFile, _isDarkMode.ToString()); }
catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error saving theme: {ex.Message}"); }
}

try
private void CloseCurrentTab()
{
if (WorkspaceTabs.SelectedItem != null) CloseTab(WorkspaceTabs.SelectedItem);
}

private void FocusSearch()
{
var contentPresenter = FindVisualChild<ContentPresenter>(WorkspaceTabs);
var searchRoot = contentPresenter ?? (DependencyObject)WorkspaceTabs;
var searchBox = FindSearchTextBox(searchRoot);

if (searchBox != null)
{
searchBox.Focus();
Keyboard.Focus(searchBox);
StatusText.Text = "Search box focused (Ctrl+F).";
}
else
{
if (!string.IsNullOrEmpty(_themeConfigFile))
StatusText.Text = "No search box found in this view.";
}
}

private void ExecuteGlobalNew()
{
var selectedItem = WorkspaceTabs.SelectedItem;
if (selectedItem == null) return;

StatusText.Text = "Opening new entry form...";

var itemType = selectedItem.GetType();
string[] commandNames = { "NewEntityCommand", "NewItemCommand", "NewCommand" };

foreach (var name in commandNames)
{
var prop = itemType.GetProperty(name);
if (prop != null && prop.GetValue(selectedItem) is ICommand command && command.CanExecute(null))
{
File.WriteAllText(_themeConfigFile, _isDarkMode.ToString());
command.Execute(null);
(WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus();
return;
}
}
catch (Exception ex)
StatusText.Text = "New entry not available here.";
}

private async void ExecuteGlobalSave()
{
var selectedItem = WorkspaceTabs.SelectedItem;
if (selectedItem == null) return;

var prop = selectedItem.GetType().GetProperty("SaveCommand");
if (prop != null && prop.GetValue(selectedItem) is ICommand command && command.CanExecute(null))
{
StatusText.Text = "Saving...";
try
{
var asyncMethod = command.GetType().GetMethod("ExecuteAsync");
if (asyncMethod != null && asyncMethod.Invoke(command, new object?[] { null }) is Task task)
{
await task;
}
else
{
command.Execute(null);
}
StatusText.Text = "Transaction saved successfully (Ctrl+S).";
}
catch (Exception ex)
{
StatusText.Text = "Save failed.";
System.Diagnostics.Debug.WriteLine($"Save error: {ex.Message}");
}

(WorkspaceTabs.SelectedContent as FrameworkElement)?.Focus();
}
else
{
StatusText.Text = "Save not supported in this view.";
}
}

private TextBox? FindSearchTextBox(DependencyObject? obj)
{
if (obj == null) return null;

for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = System.Windows.Media.VisualTreeHelper.GetChild(obj, i);

if (child is TextBox tb)
{
var binding = tb.GetBindingExpression(TextBox.TextProperty);
var path = binding?.ParentBinding?.Path?.Path;
if (path is "FilterText" or "SearchText")
return tb;
}

var result = FindSearchTextBox(child);
if (result != null) return result;
}
return null;
}

private T? FindVisualChild<T>(DependencyObject? obj) where T : DependencyObject
{
if (obj == null) return null;

for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(obj); i++)
{
System.Diagnostics.Debug.WriteLine($"Error saving theme: {ex.Message}");
var child = System.Windows.Media.VisualTreeHelper.GetChild(obj, i);
if (child is T match) return match;

var result = FindVisualChild<T>(child);
if (result != null) return result;
}
return null;
}
}

public class ActionCommand : ICommand
{
private readonly Action _execute;
public ActionCommand(Action execute) => _execute = execute;
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter) => _execute();
public event EventHandler? CanExecuteChanged { add { } remove { } }
}
Loading