diff --git a/Example/demo.cs b/Example/demo.cs
index aecbd4212..8c4eb891e 100644
--- a/Example/demo.cs
+++ b/Example/demo.cs
@@ -97,14 +97,14 @@ static class Demo {
// layout based on referencing elements of another view:
var login = new Label ("Login: ") { X = 3, Y = 6 };
- var password = new Label ("Password: ") {
- X = Pos.Left (login),
- Y = Pos.Bottom (login) + 1
+ var password = new Label ("Password: ") {
+ X = Pos.Left (login),
+ Y = Pos.Bottom (login) + 1
};
- var loginText = new TextField ("") {
- X = Pos.Right (password),
- Y = Pos.Top (login),
- Width = 40
+ var loginText = new TextField ("") {
+ X = Pos.Right (password),
+ Y = Pos.Top (login),
+ Width = 40
};
var passText = new TextField ("") {
Secret = true,
@@ -173,7 +173,12 @@ static class Demo {
});
ntop.Add (menu);
- var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "/etc/passwd");
+ var win = new Window ("/etc/passwd") {
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
+ };
ntop.Add (win);
var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
@@ -204,6 +209,37 @@ static class Demo {
Application.Run (d);
}
+ public static void ShowHex (Toplevel top)
+ {
+ var tframe = top.Frame;
+ var ntop = new Toplevel (tframe);
+ var menu = new MenuBar (new MenuBarItem [] {
+ new MenuBarItem ("_File", new MenuItem [] {
+ new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
+ }),
+ });
+ ntop.Add (menu);
+
+ var win = new Window ("/etc/passwd") {
+ X = 0,
+ Y = 1,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
+ };
+ ntop.Add (win);
+
+ var source = System.IO.File.OpenRead ("/etc/passwd");
+ var hex = new HexView (source) {
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
+ };
+ win.Add (hex);
+ Application.Run (ntop);
+
+ }
+
public static Label ml;
static void Main ()
{
@@ -211,7 +247,7 @@ static class Demo {
Application.Init ();
var top = Application.Top;
var tframe = top.Frame;
- Open ();
+ //Open ();
#if true
var win = new Window ("Hello") {
X = 0,
@@ -227,6 +263,7 @@ static class Demo {
new MenuItem ("Text Editor Demo", "", () => { Editor (top); }),
new MenuItem ("_New", "Creates new file", NewFile),
new MenuItem ("_Open", "", Open),
+ new MenuItem ("_Hex", "", () => ShowHex (top)),
new MenuItem ("_Close", "", () => Close ()),
new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
}),
diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs
index d80a6c4ff..4f69396fb 100644
--- a/Terminal.Gui/Core.cs
+++ b/Terminal.Gui/Core.cs
@@ -1247,6 +1247,15 @@ namespace Terminal.Gui {
}
return false;
}
+
+ ///
+ /// This method is invoked by Application.Begin as part of the Application.Run after
+ /// the views have been laid out, and before the views are drawn for the first time.
+ ///
+ public virtual void WillPresent ()
+ {
+ FocusFirst ();
+ }
}
///
@@ -1765,7 +1774,7 @@ namespace Terminal.Gui {
if (toplevel.LayoutStyle == LayoutStyle.Computed)
toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
toplevel.LayoutSubviews ();
- toplevel.FocusFirst ();
+ toplevel.WillPresent ();
Redraw (toplevel);
toplevel.PositionCursor ();
Driver.Refresh ();
diff --git a/Terminal.Gui/Dialogs/FileDialog.cs b/Terminal.Gui/Dialogs/FileDialog.cs
index 8f0e8df01..075a3d4af 100644
--- a/Terminal.Gui/Dialogs/FileDialog.cs
+++ b/Terminal.Gui/Dialogs/FileDialog.cs
@@ -2,16 +2,12 @@
// FileDialog.cs: File system dialogs for open and save
//
// TODO:
-// * Raise event on file selected
// * Add directory selector
-// * Update file name on cursor changes
-// * Figure out why Ok/Cancel buttons do not work
// * Implement subclasses
// * Figure out why message text does not show
// * Remove the extra space when message does not show
// * Use a line separator to show the file listing, so we can use same colors as the rest
-// * Implement support for the subclass properties.
-// * Add mouse support
+// * DirListView: Add mouse support
using System;
using System.Collections.Generic;
@@ -138,6 +134,7 @@ namespace Terminal.Gui {
public Action<(string,bool)> SelectedChanged;
public Action DirectoryChanged;
+ public Action FileChanged;
void SelectionChanged ()
{
@@ -190,12 +187,20 @@ namespace Terminal.Gui {
return true;
case Key.Enter:
- if (infos [selected].Item2) {
+ var isDir = infos [selected].Item2;
+
+ if (isDir) {
Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1));
if (DirectoryChanged != null)
DirectoryChanged (Directory);
} else {
- // File Selected
+ if (FileChanged != null)
+ FileChanged (infos [selected].Item1);
+ if (canChooseFiles) {
+ // Let the OK handler take it over
+ return false;
+ }
+ // No files allowed, do not let the default handler take it.
}
return true;
@@ -284,18 +289,18 @@ namespace Terminal.Gui {
};
dirEntry = new TextField ("") {
- X = 11,
+ X = Pos.Right (dirLabel),
Y = 1 + msgLines,
Width = Dim.Fill () - 1
};
Add (dirLabel, dirEntry);
- this.nameFieldLabel = new Label (nameFieldLabel) {
- X = 1,
+ this.nameFieldLabel = new Label ("Open: ") {
+ X = 6,
Y = 3 + msgLines,
};
nameEntry = new TextField ("") {
- X = 1 + nameFieldLabel.RuneCount + 1,
+ X = Pos.Left (dirEntry),
Y = 3 + msgLines,
Width = Dim.Fill () - 1
};
@@ -305,17 +310,37 @@ namespace Terminal.Gui {
X = 1,
Y = 3 + msgLines + 2,
Width = Dim.Fill (),
- Height = Dim.Fill ()-2,
- Directory = "."
+ Height = Dim.Fill () - 2,
};
+ DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory);
Add (dirListView);
dirListView.DirectoryChanged = (dir) => dirEntry.Text = dir;
+ dirListView.FileChanged = (file) => {
+ nameEntry.Text = file;
+ };
this.cancel = new Button ("Cancel");
AddButton (cancel);
- this.prompt = new Button (prompt);
+ this.prompt = new Button (prompt) {
+ IsDefault = true,
+ };
+ this.prompt.Clicked += () => {
+ canceled = false;
+ Application.RequestStop ();
+ };
AddButton (this.prompt);
+
+ // On success, we will set this to false.
+ canceled = true;
+ }
+
+ internal bool canceled;
+
+ public override void WillPresent ()
+ {
+ base.WillPresent ();
+ //SetFocus (nameEntry);
}
///
@@ -403,10 +428,34 @@ namespace Terminal.Gui {
}
}
+ ///
+ /// The save dialog provides an interactive dialog box for users to pick a file to
+ /// save.
+ ///
+ ///
+ ///
+ /// To use it, create an instance of the SaveDialog, and then
+ /// call Application.Run on the resulting instance. This will run the dialog modally,
+ /// and when this returns, the FileName property will contain the selected value or
+ /// null if the user canceled.
+ ///
public class SaveDialog : FileDialog {
public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message)
{
}
+
+ ///
+ /// Gets the name of the file the user selected for saving, or null
+ /// if the user canceled the dialog box.
+ ///
+ /// The name of the file.
+ public ustring FileName {
+ get {
+ if (canceled)
+ return null;
+ return FilePath;
+ }
+ }
}
///
@@ -414,9 +463,14 @@ namespace Terminal.Gui {
///
///
///
- /// The open dialog can be used to select files for opening, it can be configured to allow
- /// multiple items to be selected (based on the AllowsMultipleSelection) variable and
- /// you can control whether this should allow files or directories to be selected.
+ /// The open dialog can be used to select files for opening, it can be configured to allow
+ /// multiple items to be selected (based on the AllowsMultipleSelection) variable and
+ /// you can control whether this should allow files or directories to be selected.
+ ///
+ ///
+ /// To use it, create an instance of the OpenDialog, configure its properties, and then
+ /// call Application.Run on the resulting instance. This will run the dialog modally,
+ /// and when this returns, the list of filds will be available on the FilePaths property.
///
///
/// To select more than one file, users can use the spacebar, or control-t.
diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs
new file mode 100644
index 000000000..2662155fb
--- /dev/null
+++ b/Terminal.Gui/Views/HexView.cs
@@ -0,0 +1,214 @@
+//
+// HexView.cs: A hexadecimal viewer
+//
+// TODO:
+// - Support an operation to switch between hex and values
+// - Tab perhaps to switch?
+// - Support nibble-based navigation
+// - Support editing, perhaps via list of changes?
+// - Support selection with highlighting
+// - Redraw should support just repainted affected region
+// - Process Key needs to just queue affected region for cursor changes (as we repaint the text)
+
+using System;
+using System.IO;
+
+namespace Terminal.Gui {
+ public class HexView : View {
+ Stream source;
+ long displayStart, position;
+
+ ///
+ /// Creates and instance of the HexView that will render a seekable stream in hex on the allocated view region.
+ ///
+ /// Source stream, this stream should support seeking, or this will raise an exceotion.
+ public HexView (Stream source) : base()
+ {
+ Source = source;
+ this.source = source;
+ CanFocus = true;
+ }
+
+ ///
+ /// The source stream to display on the hex view, the stream should support seeking.
+ ///
+ /// The source.
+ public Stream Source {
+ get => source;
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("source");
+ if (!value.CanSeek)
+ throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
+ source = value;
+
+ SetNeedsDisplay ();
+ }
+ }
+
+ internal void SetDisplayStart (long value)
+ {
+ if (value >= source.Length)
+ displayStart = source.Length - 1;
+ else if (value < 0)
+ displayStart = 0;
+ else
+ displayStart = value;
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// Configures the initial offset to be displayed at the top
+ ///
+ /// The display start.
+ public long DisplayStart {
+ get => displayStart;
+ set {
+ position = value;
+
+ SetDisplayStart (value);
+ }
+ }
+
+ const int displayWidth = 9;
+ const int bsize = 4;
+ int bytesPerLine;
+
+ public override Rect Frame {
+ get => base.Frame;
+ set {
+ base.Frame = value;
+
+ // Small buffers will just show the position, with 4 bytes
+ bytesPerLine = 4;
+ if (value.Width - displayWidth > 17)
+ bytesPerLine = 4 * ((value.Width - displayWidth) / 18);
+ }
+ }
+
+ public override void Redraw (Rect region)
+ {
+ Attribute currentAttribute;
+ var current = ColorScheme.Focus;
+ Driver.SetAttribute (current);
+ Move (0, 0);
+
+ var frame = Frame;
+
+ var nblocks = bytesPerLine / 4;
+ var data = new byte [nblocks * 4 * frame.Height];
+ Source.Position = displayStart;
+ var n = source.Read (data, 0, data.Length);
+
+ for (int line = 0; line < frame.Height; line++) {
+ Move (0, line);
+ Driver.SetAttribute (ColorScheme.HotNormal);
+ Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4));
+
+ currentAttribute = ColorScheme.HotNormal;
+ SetAttribute (ColorScheme.Normal);
+
+ for (int block = 0; block < nblocks; block++) {
+ for (int b = 0; b < 4; b++) {
+ var offset = (line * nblocks * 4) + block * 4 + b;
+ if (offset + displayStart == position)
+ SetAttribute (ColorScheme.HotNormal);
+ else
+ SetAttribute (ColorScheme.Normal);
+
+ Driver.AddStr (offset >= n ? " " : string.Format ("{0:x2} ", data [offset]));
+ }
+ Driver.AddStr (block + 1 == nblocks ? " " : "| ");
+ }
+ for (int bitem = 0; bitem < nblocks * 4; bitem++) {
+ var offset = line * nblocks * 4 + bitem;
+
+ if (offset + displayStart == position)
+ SetAttribute (ColorScheme.HotFocus);
+ else
+ SetAttribute (ColorScheme.Normal);
+
+ Rune c = ' ';
+ if (offset >= n)
+ c = ' ';
+ else {
+ var b = data [offset];
+ if (b < 32)
+ c = '.';
+ else if (b > 127)
+ c = '.';
+ else
+ c = b;
+ }
+ Driver.AddRune (c);
+ }
+ }
+
+ void SetAttribute (Attribute attribute)
+ {
+ if (currentAttribute != attribute) {
+ currentAttribute = attribute;
+ Driver.SetAttribute (attribute);
+ }
+ }
+
+ }
+
+ public override void PositionCursor ()
+ {
+ var delta = (int)(position - displayStart);
+ var line = delta / bytesPerLine;
+ var item = delta % bytesPerLine;
+ var block = item / 4;
+ var column = (item % 4) * 3;
+
+ Move (displayWidth + block * 14 + column, line);
+ }
+
+ public override bool ProcessKey (KeyEvent keyEvent)
+ {
+ switch (keyEvent.Key) {
+ case Key.CursorLeft:
+ if (position == 0)
+ return true;
+ if (position - 1 < DisplayStart) {
+ SetDisplayStart (displayStart - bytesPerLine);
+ SetNeedsDisplay ();
+ }
+ position--;
+ break;
+ case Key.CursorRight:
+ if (position < source.Length)
+ position++;
+ if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
+ SetDisplayStart (DisplayStart + bytesPerLine);
+ SetNeedsDisplay ();
+ }
+ break;
+ case Key.CursorDown:
+ if (position + bytesPerLine < source.Length)
+ position += bytesPerLine;
+ if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
+ SetDisplayStart (DisplayStart + bytesPerLine);
+ SetNeedsDisplay ();
+ }
+ break;
+ case Key.CursorUp:
+ position -= bytesPerLine;
+ if (position < 0)
+ position = 0;
+ if (position < DisplayStart) {
+ SetDisplayStart (DisplayStart - bytesPerLine);
+ SetNeedsDisplay ();
+ }
+ break;
+ default:
+ return false;
+ }
+ // TODO: just se the NeedDispay for the affected region, not all
+ SetNeedsDisplay ();
+ PositionCursor ();
+ return false;
+ }
+ }
+}