FileDialog work, HexViewer view

This commit is contained in:
Miguel de Icaza
2018-05-09 23:12:06 -04:00
parent 31ae05af42
commit 4cf9c7b138
4 changed files with 341 additions and 27 deletions

View File

@@ -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; })
}),

View File

@@ -1247,6 +1247,15 @@ namespace Terminal.Gui {
}
return false;
}
/// <summary>
/// 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.
/// </summary>
public virtual void WillPresent ()
{
FocusFirst ();
}
}
/// <summary>
@@ -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 ();

View File

@@ -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<ustring> DirectoryChanged;
public Action<ustring> 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);
}
/// <summary>
@@ -403,10 +428,34 @@ namespace Terminal.Gui {
}
}
/// <summary>
/// The save dialog provides an interactive dialog box for users to pick a file to
/// save.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </remarks>
public class SaveDialog : FileDialog {
public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message)
{
}
/// <summary>
/// Gets the name of the file the user selected for saving, or null
/// if the user canceled the dialog box.
/// </summary>
/// <value>The name of the file.</value>
public ustring FileName {
get {
if (canceled)
return null;
return FilePath;
}
}
}
/// <summary>
@@ -414,9 +463,14 @@ namespace Terminal.Gui {
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// To select more than one file, users can use the spacebar, or control-t.

View File

@@ -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;
/// <summary>
/// Creates and instance of the HexView that will render a seekable stream in hex on the allocated view region.
/// </summary>
/// <param name="source">Source stream, this stream should support seeking, or this will raise an exceotion.</param>
public HexView (Stream source) : base()
{
Source = source;
this.source = source;
CanFocus = true;
}
/// <summary>
/// The source stream to display on the hex view, the stream should support seeking.
/// </summary>
/// <value>The source.</value>
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 ();
}
/// <summary>
/// Configures the initial offset to be displayed at the top
/// </summary>
/// <value>The display start.</value>
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;
}
}
}