mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
FileDialog work, HexViewer view
This commit is contained in:
@@ -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; })
|
||||
}),
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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.
|
||||
|
||||
214
Terminal.Gui/Views/HexView.cs
Normal file
214
Terminal.Gui/Views/HexView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user