From f1980e289f31a3fc34e9777ea8ff53b4695f785b Mon Sep 17 00:00:00 2001 From: Eusebiu Marcu Date: Tue, 16 Nov 2010 17:00:44 +0200 Subject: [PATCH] WPF convert LoadedModules and RunningThreads pads --- .../Debugger.AddIn/Debugger.AddIn.csproj | 6 + .../Debugger.AddIn/Pads/ConsolePad.cs | 5 +- .../Pads/Controls/SimpleListViewControl.xaml | 16 ++ .../Controls/SimpleListViewControl.xaml.cs | 62 ++++++++ .../Debugger.AddIn/Pads/LoadedModulesPad.cs | 98 +++++-------- .../Pads/RunningThreadsPad.Menu.cs | 52 ++++--- .../Debugger.AddIn/Pads/RunningThreadsPad.cs | 137 ++++++++---------- 7 files changed, 208 insertions(+), 168 deletions(-) create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml create mode 100644 src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml.cs diff --git a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj index 8ff2e605e0..6361e00f64 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj +++ b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.csproj @@ -58,6 +58,7 @@ ..\..\..\..\bin\SharpDevelop.exe + 3.0 @@ -95,6 +96,10 @@ ConditionCell.xaml + + SimpleListViewControl.xaml + Code + WatchList.xaml @@ -345,6 +350,7 @@ + diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs index 7b3470a483..d80484b2ea 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs @@ -2,16 +2,13 @@ // This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) using System; -using System.Collections; using System.Windows.Controls; using System.Windows.Input; + using Debugger; -using Debugger.AddIn; -using ICSharpCode.Core; using ICSharpCode.Core.Presentation; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Visitors; -using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Debugging; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver; diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml b/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml new file mode 100644 index 0000000000..8ef39ed7d6 --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml.cs new file mode 100644 index 0000000000..737bc2e7fd --- /dev/null +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/Controls/SimpleListViewControl.xaml.cs @@ -0,0 +1,62 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Dynamic; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; + +namespace Debugger.AddIn.Pads.Controls +{ + public partial class SimpleListViewControl : UserControl + { + public event EventHandler ItemActivated; + + private ObservableCollection itemCollection = new ObservableCollection(); + + public SimpleListViewControl() + { + InitializeComponent(); + + ItemsListView.MouseDoubleClick += new MouseButtonEventHandler(ItemsListView_MouseDoubleClick); + } + + public ObservableCollection ItemCollection { + get { return itemCollection; } + } + + public IList SelectedItems { + get { + var result = new List(); + foreach (var item in ItemsListView.SelectedItems) + result.Add((ExpandoObject)item); + + return result; + } + } + + public void ClearColumns() + { + ((GridView)this.ItemsListView.View).Columns.Clear(); + } + + public void AddColumn(string header, Binding binding, double width) + { + GridViewColumn column = new GridViewColumn(); + column.Width = width; + column.DisplayMemberBinding = binding; + column.Header = header; + ((GridView)this.ItemsListView.View).Columns.Add(column); + } + + void ItemsListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + var handler = ItemActivated; + if (handler != null) + handler(this, EventArgs.Empty); + } + } +} \ No newline at end of file diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/LoadedModulesPad.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/LoadedModulesPad.cs index 379a63d939..0ccfc5c182 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/LoadedModulesPad.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/LoadedModulesPad.cs @@ -2,27 +2,21 @@ // This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) using System; -using System.Windows.Forms; +using System.Dynamic; +using System.Windows; +using System.Windows.Data; + using Debugger; +using Debugger.AddIn.Pads.Controls; using ICSharpCode.Core; namespace ICSharpCode.SharpDevelop.Gui.Pads { public class LoadedModulesPad : DebuggerPad { - ListView loadedModulesList; + SimpleListViewControl loadedModulesList; Process debuggedProcess; - ColumnHeader name = new ColumnHeader(); - ColumnHeader address = new ColumnHeader(); - ColumnHeader path = new ColumnHeader(); - ColumnHeader order = new ColumnHeader(); - ColumnHeader version = new ColumnHeader(); - ColumnHeader program = new ColumnHeader(); - ColumnHeader timestamp = new ColumnHeader(); - ColumnHeader information = new ColumnHeader(); - - public override object Control { get { return loadedModulesList; @@ -31,41 +25,26 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads protected override void InitializeComponents() { - loadedModulesList = new ListView(); - loadedModulesList.FullRowSelect = true; - loadedModulesList.AutoArrange = true; - loadedModulesList.Alignment = ListViewAlignment.Left; - loadedModulesList.View = View.Details; - loadedModulesList.Dock = DockStyle.Fill; - loadedModulesList.GridLines = false; - loadedModulesList.Activation = ItemActivation.OneClick; - loadedModulesList.Columns.AddRange(new ColumnHeader[] {name, address, path, order, version, program, timestamp, information} ); - name.Width = 250; - address.Width = 100; - path.Width = 250; - order.Width = 80; - version.Width = 0;//50; - program.Width = 0;//90; - timestamp.Width = 0;//80; - information.Width = 130; - + loadedModulesList = new SimpleListViewControl(); RedrawContent(); ResourceService.LanguageChanged += delegate { RedrawContent(); }; } public void RedrawContent() { - name.Text = StringParser.Parse("${res:Global.Name}"); - address.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.AddressColumn}"); - path.Text = StringParser.Parse("${res:Global.Path}"); - order.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.OrderColumn}"); - version.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.VersionColumn}"); - program.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.ProgramColumn}"); - timestamp.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.TimestampColumn}"); - information.Text = StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.SymbolsColumn}"); + loadedModulesList.ClearColumns(); + loadedModulesList.AddColumn(StringParser.Parse("${res:Global.Name}"), + new Binding { Path = new PropertyPath("Name") }, 250); + loadedModulesList.AddColumn(StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.AddressColumn}"), + new Binding { Path = new PropertyPath("Address") }, 100); + loadedModulesList.AddColumn(StringParser.Parse("${res:Global.Path}"), + new Binding { Path = new PropertyPath("Path") }, 250); + loadedModulesList.AddColumn(StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.OrderColumn}"), + new Binding { Path = new PropertyPath("Order") }, 80); + loadedModulesList.AddColumn(StringParser.Parse("${res:MainWindow.Windows.Debug.Modules.SymbolsColumn}"), + new Binding { Path = new PropertyPath("Symbols") }, 130); } - protected override void SelectProcess(Process process) { if (debuggedProcess != null) { @@ -92,7 +71,7 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads public override void RefreshPad() { - loadedModulesList.Items.Clear(); + loadedModulesList.ItemCollection.Clear(); if (debuggedProcess != null) { foreach(Module module in debuggedProcess.Modules) { AddModule(module); @@ -102,39 +81,32 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads void AddModule(Module module) { - ListViewItem newItem = new ListViewItem(); - newItem.Tag = module; - RefreshItem(newItem); - module.SymbolsUpdated += delegate { RefreshItem(newItem); }; - loadedModulesList.Items.Add(newItem); + dynamic obj = new ExpandoObject(); + obj.Tag = module; + RefreshItem(obj); + module.SymbolsUpdated += delegate { RefreshItem(obj); }; + loadedModulesList.ItemCollection.Add(obj); } - void RefreshItem(ListViewItem item) + void RefreshItem(ExpandoObject obj) { + dynamic item = obj; Module module = (Module)item.Tag; - item.SubItems.Clear(); - item.SubItems.AddRange( - new string[] { - module.Name, - String.Format("{0:X8}", module.BaseAdress), - module.IsDynamic ? "(dynamic)" : module.IsInMemory ? "(in memory)" : module.FullPath, - module.OrderOfLoading.ToString(), - "", - "", - "", - StringParser.Parse(module.HasSymbols ? "${res:MainWindow.Windows.Debug.Modules.HasSymbols}" : "${res:MainWindow.Windows.Debug.Modules.HasNoSymbols}") - } - ); - item.SubItems.RemoveAt(0); + item.Name = module.Name; + item.Address = String.Format("{0:X8}", module.BaseAdress); + item.Path = module.IsDynamic ? "(dynamic)" : module.IsInMemory ? "(in memory)" : module.FullPath; + item.Order = module.OrderOfLoading.ToString(); + item.Symbols = StringParser.Parse(module.HasSymbols ? "${res:MainWindow.Windows.Debug.Modules.HasSymbols}" : "${res:MainWindow.Windows.Debug.Modules.HasNoSymbols}"); } void RemoveModule(Module module) { - foreach (ListViewItem item in loadedModulesList.Items) { + foreach (dynamic item in loadedModulesList.ItemCollection) { if (item.Tag == module) { - item.Remove(); + loadedModulesList.ItemCollection.Remove(item); + break; } } } } -} +} \ No newline at end of file diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.Menu.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.Menu.cs index 3cf50d60de..83d07ee631 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.Menu.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.Menu.cs @@ -1,8 +1,11 @@ // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) -using System.ComponentModel; -using System.Windows.Forms; +using System.Collections; +using System.Dynamic; +using System.Windows; +using System.Windows.Controls; + using Debugger; using ICSharpCode.Core; @@ -10,55 +13,50 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads { public partial class RunningThreadsPad { - ContextMenuStrip CreateContextMenuStrip() + ContextMenu CreateContextMenuStrip() { - ContextMenuStrip menu = new ContextMenuStrip(); - menu.Opening += FillContextMenuStrip; + ContextMenu menu = new ContextMenu(); + menu.Opened += FillContextMenuStrip; return menu; } - - void FillContextMenuStrip(object sender, CancelEventArgs e) + + void FillContextMenuStrip(object sender, RoutedEventArgs e) { - ListView.SelectedListViewItemCollection items = runningThreadsList.SelectedItems; - - if (items.Count == 0) { - e.Cancel = true; + var items = runningThreadsList.SelectedItems; + if (items == null || items.Count == 0) { + e.Handled = true; return; } - ListViewItem item = items[0]; + dynamic item = items[0]; - ContextMenuStrip menu = sender as ContextMenuStrip; + ContextMenu menu = sender as ContextMenu; menu.Items.Clear(); - ToolStripMenuItem freezeItem; - freezeItem = new ToolStripMenuItem(); - freezeItem.Text = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Freeze"); - freezeItem.Checked = (item.Tag as Thread).Suspended; + MenuItem freezeItem; + freezeItem = new MenuItem(); + freezeItem.Header = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Freeze"); + freezeItem.IsChecked = (item.Tag as Thread).Suspended; freezeItem.Click += delegate { - ListView.SelectedListViewItemCollection selItems = runningThreadsList.SelectedItems; - if (selItems.Count == 0) { + if (items == null || items.Count == 0) { + e.Handled = true; return; } - bool suspended = (selItems[0].Tag as Thread).Suspended; + bool suspended = (item.Tag as Thread).Suspended; if (!debuggedProcess.IsPaused) { MessageService.ShowMessage("${res:MainWindow.Windows.Debug.Threads.CannotFreezeWhileRunning}", "${res:MainWindow.Windows.Debug.Threads.Freeze}"); return; } - foreach(ListViewItem i in selItems) { - (i.Tag as Thread).Suspended = !suspended; + foreach(dynamic current in items) { + (current.Tag as Thread).Suspended = !suspended; } RefreshPad(); }; - menu.Items.AddRange(new ToolStripItem[] { - freezeItem, - }); - - e.Cancel = false; + menu.Items.Add(freezeItem); } } } diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.cs index c8adde832d..07998ff443 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/RunningThreadsPad.cs @@ -2,51 +2,36 @@ // This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) using System; +using System.Dynamic; using System.Threading; -using System.Windows.Forms; +using System.Windows; +using System.Windows.Data; + using Debugger; +using Debugger.AddIn.Pads.Controls; using Debugger.AddIn.TreeModel; using ICSharpCode.Core; -using Exception=System.Exception; -using Thread=Debugger.Thread; +using Exception = System.Exception; +using Thread = Debugger.Thread; namespace ICSharpCode.SharpDevelop.Gui.Pads { public partial class RunningThreadsPad : DebuggerPad { - ListView runningThreadsList; + SimpleListViewControl runningThreadsList; Process debuggedProcess; - ColumnHeader id = new ColumnHeader(); - ColumnHeader name = new ColumnHeader(); - ColumnHeader location = new ColumnHeader(); - ColumnHeader priority = new ColumnHeader(); - ColumnHeader breaked = new ColumnHeader(); - public override object Control { get { return runningThreadsList; } } - + protected override void InitializeComponents() { - runningThreadsList = new ListView(); - runningThreadsList.FullRowSelect = true; - runningThreadsList.AutoArrange = true; - runningThreadsList.Alignment = ListViewAlignment.Left; - runningThreadsList.View = View.Details; - runningThreadsList.Dock = DockStyle.Fill; - runningThreadsList.GridLines = false; - runningThreadsList.Activation = ItemActivation.OneClick; - runningThreadsList.Columns.AddRange(new ColumnHeader[] {id, name, location, priority, breaked} ); - runningThreadsList.ContextMenuStrip = CreateContextMenuStrip(); - runningThreadsList.ItemActivate += new EventHandler(RunningThreadsListItemActivate); - id.Width = 100; - name.Width = 300; - location.Width = 250; - priority.Width = 120; - breaked.Width = 80; + runningThreadsList = new SimpleListViewControl(); + runningThreadsList.ContextMenu = CreateContextMenuStrip(); + runningThreadsList.ItemActivated += RunningThreadsListItemActivate; RedrawContent(); ResourceService.LanguageChanged += delegate { RedrawContent(); }; @@ -54,13 +39,23 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads public void RedrawContent() { - id.Text = ResourceService.GetString("Global.ID"); - name.Text = ResourceService.GetString("Global.Name"); - location.Text = ResourceService.GetString("AddIns.HtmlHelp2.Location"); - priority.Text = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority"); - breaked.Text = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Frozen"); + runningThreadsList.ClearColumns(); + runningThreadsList.AddColumn(ResourceService.GetString("Global.ID"), + new Binding { Path = new PropertyPath("ID") }, + 100); + runningThreadsList.AddColumn(ResourceService.GetString("Global.Name"), + new Binding { Path = new PropertyPath("Name") }, + 300); + runningThreadsList.AddColumn(ResourceService.GetString("AddIns.HtmlHelp2.Location"), + new Binding { Path = new PropertyPath("Location") }, + 250); + runningThreadsList.AddColumn(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority"), + new Binding { Path = new PropertyPath("Priority") }, + 120); + runningThreadsList.AddColumn(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Frozen"), + new Binding { Path = new PropertyPath("Frozen") }, + 80); } - protected override void SelectProcess(Process process) { @@ -73,7 +68,7 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads debuggedProcess.Paused += debuggedProcess_Paused; debuggedProcess.Threads.Added += debuggedProcess_ThreadStarted; } - runningThreadsList.Items.Clear(); + runningThreadsList.ItemCollection.Clear(); RefreshPad(); } @@ -90,7 +85,7 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads public override void RefreshPad() { if (debuggedProcess == null || debuggedProcess.IsRunning) { - runningThreadsList.Items.Clear(); + runningThreadsList.ItemCollection.Clear(); return; } @@ -100,7 +95,7 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads if (debuggedProcess.IsPaused) { Utils.DoEvents(debuggedProcess); } - RefreshThread(t); + AddThread(t); } } catch(AbortedBecauseDebuggeeResumedException) { } catch(Exception) { @@ -117,86 +112,80 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads { if (debuggedProcess.IsPaused) { if (debuggedProcess != null) { - debuggedProcess.SelectedThread = (Thread)(runningThreadsList.SelectedItems[0].Tag); + dynamic obj = runningThreadsList.SelectedItems[0]; + debuggedProcess.SelectedThread = (Thread)(obj.Tag); debuggedProcess.OnPaused(); // Force refresh of pads } } else { MessageService.ShowMessage("${res:MainWindow.Windows.Debug.Threads.CannotSwitchWhileRunning}", "${res:MainWindow.Windows.Debug.Threads.ThreadSwitch}"); } - } + } void AddThread(Thread thread) { - runningThreadsList.Items.Add(new ListViewItem(thread.ID.ToString())); - RefreshThread(thread); + // remove the object if exists + RemoveThread(thread); + + dynamic obj = new ExpandoObject(); + obj.Tag = thread; + RefreshItem(obj); + runningThreadsList.ItemCollection.Add(obj); thread.NameChanged += delegate { - RefreshThread(thread); + RefreshItem(obj); }; thread.Exited += delegate { - RemoveThread(thread); + RemoveThread(obj); }; } - ListViewItem FindItem(Thread thread) + void RefreshItem(ExpandoObject obj) { - foreach (ListViewItem item in runningThreadsList.Items) { - if (item.Text == thread.ID.ToString()) { - return item; - } - } - return null; - } - - void RefreshThread(Thread thread) - { - ListViewItem item = FindItem(thread); - if (item == null) { - AddThread(thread); - return; - } - item.SubItems.Clear(); - item.Text = thread.ID.ToString(); + dynamic item = obj; + var thread = item.Tag as Thread; + + item.ID = thread.ID; item.Tag = thread; - item.SubItems.Add(thread.Name); StackFrame location = null; if (thread.Process.IsPaused) { location = thread.MostRecentStackFrame; } if (location != null) { - item.SubItems.Add(location.MethodInfo.Name); + item.Location = location.MethodInfo.Name; } else { - item.SubItems.Add(ResourceService.GetString("Global.NA")); + item.Location = ResourceService.GetString("Global.NA"); } + switch (thread.Priority) { case ThreadPriority.Highest: - item.SubItems.Add(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Highest")); + item.Priority = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Highest"); break; case ThreadPriority.AboveNormal: - item.SubItems.Add(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.AboveNormal")); + item.Priority = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.AboveNormal"); break; case ThreadPriority.Normal: - item.SubItems.Add(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Normal")); + item.Priority = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Normal"); break; case ThreadPriority.BelowNormal: - item.SubItems.Add(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.BelowNormal")); + item.Priority = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.BelowNormal"); break; case ThreadPriority.Lowest: - item.SubItems.Add(ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Lowest")); + item.Priority = ResourceService.GetString("MainWindow.Windows.Debug.Threads.Priority.Lowest"); break; default: - item.SubItems.Add(thread.Priority.ToString()); + item.Priority = thread.Priority.ToString(); break; } - item.SubItems.Add(ResourceService.GetString(thread.Suspended ? "Global.Yes" : "Global.No")); + item.Frozen = ResourceService.GetString(thread.Suspended ? "Global.Yes" : "Global.No"); } void RemoveThread(Thread thread) { - foreach (ListViewItem item in runningThreadsList.Items) { - if (thread.ID.ToString() == item.Text) { - item.Remove(); + foreach (dynamic item in runningThreadsList.ItemCollection) { + if (thread.ID == item.ID) { + runningThreadsList.ItemCollection.Remove(item); + break; } } } } -} +} \ No newline at end of file