diff --git a/iHexProject.xml b/iHexProject.xml
--- a/iHexProject.xml
+++ b/iHexProject.xml
@@ -1,145 +1,146 @@
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
./Makefile.linux
.\iHex.sln
gcc
./iHex
./iHex
../../Lgi/trunk/include
./Resources
../../Lgi/trunk/include
./Resources
../../Lgi/trunk/include/lgi/linux
../../Lgi/trunk/include/lgi/linux/Gtk
../../Lgi/trunk/include/lgi/linux
../../Lgi/trunk/include/lgi/linux/Gtk
../../Lgi/trunk/include/lgi/win
../../Lgi/trunk/include/lgi/win
Executable
iHex
iHex
magic
-static-libgcc
`pkg-config --libs gtk+-3.0`
magic
-static-libgcc
`pkg-config --libs gtk+-3.0`
/usr/include/gstreamer-1.0
`pkg-config --cflags gtk+-3.0`
/usr/include/gstreamer-1.0
`pkg-config --cflags gtk+-3.0`
+
+
+
+
-
-
-
-
4
4
0
-
-
-
-
-
-
-
-
-
-
/home/matthew/dsd-DMS-86898/sapphire/build/test.dsf
+
+
+
+
+
+
+
+
+
+
diff --git a/src/iHex.cpp b/src/iHex.cpp
--- a/src/iHex.cpp
+++ b/src/iHex.cpp
@@ -1,3459 +1,3468 @@
/*hdr
** FILE: iHex.cpp
** AUTHOR: Matthew Allen
** DATE: 7/5/2002
** DESCRIPTION: Hex viewer/editor
**
** Copyright (C) 2002, Matthew Allen
** fret@memecode.com
*/
/*
Hex view line format:
Hex: [2ch hex][:][8ch hex][2ch space][3*16=48ch hex bytes][2ch space][16ch ascii]
Bin: [11ch decimal ][2ch space][3*16=48ch hex bytes][2ch space][16ch ascii]
*/
#define _WIN32_WINNT 0x0400
#include "iHex.h"
#include "lgi/common/Token.h"
#include "lgi/common/About.h"
#include "lgi/common/Combo.h"
#include "lgi/common/ScrollBar.h"
#include "lgi/common/ProgressDlg.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/Combo.h"
#include "lgi/common/Menu.h"
#include "lgi/common/ToolBar.h"
#include "lgi/common/StatusBar.h"
#include "lgi/common/Charset.h"
#include "resdefs.h"
#include "Diff.h"
#include "iHexView.h"
#ifdef WIN32
#include "wincrypt.h"
#include "resource.h"
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
// Application identification
const char *AppName = "i.Hex";
const char *Untitled = "Untitled Document";
bool CancelSearch = false;
#define DEBUG_COVERAGE_CHECK 0
#define ColourSelectionFore LColour(255, 255, 0)
#define ColourSelectionBack LColour(0, 0, 255)
#define CursorColourBack LColour(192, 192, 192)
#define HEX_COLUMN 13 // characters, location of first files hex column
#define TEXT_COLUMN (HEX_COLUMN + (3 * BytesPerLine) + GAP_HEX_ASCII)
#define GAP_HEX_ASCII 2 // characters, this is the gap between the hex and ascii columns
#define GAP_FILES 6 // characters, this is the gap between 2 files when comparing
#define FILE_BUFFER_SIZE 1024
#define UI_UPDATE_SPEED 500 // ms
LColour ChangedFore(0xf1, 0xe2, 0xad);
LColour ChangedBack(0xef, 0xcb, 0x05);
LColour DeletedBack(0xc0, 0xc0, 0xc0);
///////////////////////////////////////////////////////////////////////////////////////////////
class RandomData : public LStream
{
#ifdef WIN32
typedef BOOLEAN (APIENTRY *RtlGenRandom)(void*, ULONG);
HCRYPTPROV phProv;
HMODULE hADVAPI32;
RtlGenRandom GenRandom;
#endif
public:
RandomData()
{
#ifdef WIN32
phProv = 0;
hADVAPI32 = 0;
GenRandom = 0;
if (!CryptAcquireContext(&phProv, 0, 0, PROV_RSA_FULL, 0))
{
// f***ing windows... try a different strategy.
hADVAPI32 = LoadLibrary("ADVAPI32.DLL");
if (hADVAPI32)
{
GenRandom = (RtlGenRandom) GetProcAddress(hADVAPI32, "SystemFunction036");
}
}
#endif
}
~RandomData()
{
#ifdef WIN32
if (phProv)
CryptReleaseContext(phProv, 0);
else if (hADVAPI32)
FreeLibrary(hADVAPI32);
#endif
}
ssize_t Read(void *Ptr, ssize_t Len, int Flags = 0)
{
#ifdef WIN32
if (phProv)
{
if (CryptGenRandom(phProv, Len, (uchar*)Ptr))
return Len;
}
else if (GenRandom)
{
if (GenRandom(Ptr, Len))
return Len;
}
#endif
return 0;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////
class ChangeSizeDlg : public LDialog
{
int OldUnits;
public:
int64 Size;
ChangeSizeDlg(AppWnd *app, int64 size)
{
SetParent(app);
if (LoadFromResource(IDD_CHANGE_FILE_SIZE))
{
MoveToCenter();
LCombo *Units = dynamic_cast(FindControl(IDC_UNITS));
if (Units)
{
Units->Insert("bytes");
Units->Insert("KB");
Units->Insert("MB");
Units->Insert("GB");
if (size < 10 << 10)
{
SetCtrlValue(IDC_UNITS, 0);
}
else if (size < 1 << 20)
{
SetCtrlValue(IDC_UNITS, 1);
}
else if (size < 1 << 30)
{
SetCtrlValue(IDC_UNITS, 2);
}
else
{
SetCtrlValue(IDC_UNITS, 3);
}
SetBytes(size);
OnNotify(FindControl(IDC_NUMBER), LNotifyValueChanged);
}
}
}
void SetBytes(int64 size)
{
switch (GetCtrlValue(IDC_UNITS))
{
case 0:
{
char s[64];
sprintf(s, LPrintfInt64, size);
SetCtrlName(IDC_NUMBER, s);
break;
}
case 1:
{
char s[64];
double d = (double)size / 1024.0;
sprintf(s, "%f", d);
SetCtrlName(IDC_NUMBER, s);
break;
}
case 2:
{
char s[64];
double d = (double)size / 1024.0 / 1024.0;
sprintf(s, "%f", d);
SetCtrlName(IDC_NUMBER, s);
break;
}
case 3:
{
char s[64];
double d = (double)size / 1024.0 / 1024.0 / 1024.0;
sprintf(s, "%f", d);
SetCtrlName(IDC_NUMBER, s);
break;
}
}
OldUnits = (int)GetCtrlValue(IDC_UNITS);
}
int64 GetBytes(int Units = -1)
{
int64 n = 0;
const char *s = GetCtrlName(IDC_NUMBER);
if (s)
{
switch (Units >= 0 ? Units : GetCtrlValue(IDC_UNITS))
{
case 0: // bytes
{
n = atoi64(s);
break;
}
case 1: // KB
{
n = (int64) (atof(s) * 1024.0);
break;
}
case 2: // MB
{
n = (int64) (atof(s) * 1024.0 * 1024.0);
break;
}
case 3: // GB
{
n = (int64) (atof(s) * 1024.0 * 1024.0 * 1024.0);
break;
}
}
}
return n;
}
int OnNotify(LViewI *c, LNotification n)
{
if (!c) return 0;
switch (c->GetId())
{
case IDC_UNITS:
{
SetBytes(Size);
break;
}
case IDC_NUMBER:
{
Size = GetBytes();
char s[64];
sprintf(s, "(" LPrintfInt64 " bytes)", GetBytes());
SetCtrlName(IDC_BYTE_SIZE, s);
break;
}
case IDOK:
case IDCANCEL:
{
EndModal(c->GetId() == IDOK);
break;
}
}
return 0;
}
};
class IHexBar : public LLayout, public LResourceLoad
{
friend class AppWnd;
AppWnd *App;
LHexView *View;
int Y;
public:
bool NotifyOff;
IHexBar(AppWnd *a, int y);
~IHexBar();
bool IsHex();
int64 GetOffset(int IsHex = -1, bool *Select = 0);
void SetOffset(int64 Offset);
bool IsSigned();
bool IsLittleEndian();
bool Pour(LRegion &r);
void OnPaint(LSurface *pDC);
int OnNotify(LViewI *c, LNotification n);
};
/////////////////////////////////////////////////////////////////////////////////////
IHexBar::IHexBar(AppWnd *a, int y)
{
App = a;
View = NULL;
Y = y;
_IsToolBar = true;
NotifyOff = false;
Attach(App);
LoadFromResource(IDD_HEX, this);
for (auto c: Children)
{
LRect r = c->GetPos();
r.Offset(1, 4);
c->SetPos(r);
}
AttachChildren();
LVariant v;
if (!a->GetOptions() || !a->GetOptions()->GetValue("IsHex", v))
v = true;
bool HexFmt = v.CastInt32() != 0;
SetCtrlValue(IDC_IS_HEX, HexFmt);
if (!a->GetOptions() || !a->GetOptions()->GetValue("LittleEndian", v))
v = true;
SetCtrlValue(IDC_LITTLE, v.CastInt32());
SetOffset(0);
}
IHexBar::~IHexBar()
{
if (App && App->GetOptions())
{
LVariant v;
App->GetOptions()->SetValue("IsHex", v = GetCtrlValue(IDC_IS_HEX));
App->GetOptions()->SetValue("LittleEndian", v = GetCtrlValue(IDC_LITTLE));
}
}
bool IHexBar::Pour(LRegion &r)
{
LRect *Best = FindLargestEdge(r, GV_EDGE_TOP);
if (Best)
{
LRect r = *Best;
if (r.Y() != Y)
r.y2 = r.y1 + Y - 1;
SetPos(r, true);
return true;
}
return false;
}
void IHexBar::OnPaint(LSurface *pDC)
{
LRect r = GetClient();
// LThinBorder(pDC, r, DefaultRaisedEdge);
pDC->Colour(L_MED);
pDC->Rectangle(&r);
#define Divider(x) \
pDC->Colour(L_LOW); \
pDC->Line(x, 1, x, pDC->Y()); \
pDC->Colour(L_WHITE); \
pDC->Line(x+1, 1, x+1, pDC->Y());
Divider(134);
Divider(268);
}
bool IHexBar::IsSigned()
{
return GetCtrlValue(IDC_SIGNED);
}
bool IHexBar::IsLittleEndian()
{
return GetCtrlValue(IDC_LITTLE);
}
bool IHexBar::IsHex()
{
return GetCtrlValue(IDC_IS_HEX) != 0;
}
void IHexBar::SetOffset(int64 Offset)
{
if (IsHex())
{
LString s;
s.Printf("0x%" PRIx64, Offset);
SetCtrlName(IDC_OFFSET, s);
}
else
{
SetCtrlValue(IDC_OFFSET, Offset);
}
}
int64 IHexBar::GetOffset(int IsHex, bool *Select)
{
int64 c = -1;
LString s = GetCtrlName(IDC_OFFSET);
LString Src;
Src.Printf("return %s;", s.Get());
LScriptEngine Eng(this, NULL, NULL);
LVariant Ret;
LCompiledCode Code;
LExecutionStatus Status = Eng.RunTemporary(&Code, Src, &Ret);
if (Status != ScriptError)
{
c = Ret.CastInt64();
}
return c;
}
int IHexBar::OnNotify(LViewI *c, LNotification n)
{
if (NotifyOff)
return 0;
switch (c->GetId())
{
case IDC_SIGNED:
case IDC_LITTLE:
{
if (View)
{
View->DoInfo();
View->Focus(true);
}
break;
}
case IDC_OFFSET:
{
if (View &&
n.Type == LNotifyReturnKey)
{
// Set the cursor
bool Select = false;
int64 Off = GetOffset(-1, &Select);
View->SetCursor(NULL, Select ? Off - 1 : Off, Select, Select);
// Return focus to the view
View->Focus(true);
}
break;
}
case IDC_IS_HEX:
{
if (!View)
break;
bool HexFmt = IsHex();
auto i = GetCtrlName(IDC_OFFSET);
if (i)
{
int64 NewAddr = -1;
if (HexFmt)
// Dec -> Hex
NewAddr = Atoi(i);
else
// Hex -> Dec
NewAddr = htoi(i);
if (NewAddr >= 0)
SetOffset(NewAddr);
}
// Tell the hex view
View->SetIsHex(HexFmt);
// Return focus to the view
View->Focus(true);
break;
}
case IDC_BIT7:
{
if (View) View->SetBit(0x80, c->Value());
break;
}
case IDC_BIT6:
{
if (View) View->SetBit(0x40, c->Value());
break;
}
case IDC_BIT5:
{
if (View) View->SetBit(0x20, c->Value());
break;
}
case IDC_BIT4:
{
if (View) View->SetBit(0x10, c->Value());
break;
}
case IDC_BIT3:
{
if (View) View->SetBit(0x08, c->Value());
break;
}
case IDC_BIT2:
{
if (View) View->SetBit(0x04, c->Value());
break;
}
case IDC_BIT1:
{
if (View) View->SetBit(0x02, c->Value());
break;
}
case IDC_BIT0:
{
if (View) View->SetBit(0x01, c->Value());
break;
}
case IDC_DEC_1:
{
if (View && n.Type == LNotifyReturnKey) View->SetByte(atoi(c->Name()));
break;
}
case IDC_HEX_1:
{
if (View && n.Type == LNotifyReturnKey) View->SetByte(htoi(c->Name()));
break;
}
case IDC_DEC_2:
{
if (View && n.Type == LNotifyReturnKey) View->SetShort(atoi(c->Name()));
break;
}
case IDC_HEX_2:
{
if (View && n.Type == LNotifyReturnKey) View->SetShort(htoi(c->Name()));
break;
}
case IDC_DEC_4:
{
if (View && n.Type == LNotifyReturnKey) View->SetInt(atoi(c->Name()));
break;
}
case IDC_HEX_4:
{
if (View && n.Type == LNotifyReturnKey) View->SetInt(htoi(c->Name()));
break;
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
bool LHexBuffer::Save()
{
if (!File ||
!File->IsOpen())
{
LgiTrace("%s:%i - No open file.\n", _FL);
return false;
}
if (File->SetPos(BufPos) != BufPos)
{
LgiTrace("%s:%i - Failed to set pos: " LPrintfInt64 ".\n", _FL, BufPos);
return false;
}
auto Wr = File->Write(Buf, BufUsed);
if (Wr != BufUsed)
{
LgiTrace("%s:%i - Failed to write %i bytes: %i.\n", _FL, BufUsed, Wr);
return false;
}
IsDirty = false;
return true;
}
void LHexBuffer::SetDirty(bool Dirty)
{
if (IsDirty ^ Dirty)
{
IsDirty = Dirty;
if (Dirty)
{
View->App->SetDirty(true, NULL);
}
}
}
bool LHexBuffer::GetData(int64 Start, size_t Len)
{
bool Status = false;
// is the range outside the buffer's bounds?
if (Start < 0 || Start + Len > Size)
{
return false;
}
if (!IsAsking) // && File && File->IsOpen()
{
// is the buffer allocated
if (!Buf)
{
BufLen = FILE_BUFFER_SIZE << 10;
BufPos = 1;
Buf = new uchar[BufLen];
LAssert(Buf);
}
// is the cursor outside the buffer?
if (Start < BufPos || (Start + Len) > (BufPos + BufLen))
{
// clear changes
IsAsking = true;
// FIXME: Doesn't return a valid status... convert to callback?
View->App->SetDirty(false, [this, Start, Len](auto IsClean)
{
IsAsking = false;
if (IsClean)
{
// move buffer to cover cursor pos
auto Half = BufLen >> 1;
BufPos = Start - (Half ? (Start % Half) : 0);
if (File)
{
if (File->Seek(BufPos, SEEK_SET) == BufPos)
{
memset(Buf, 0xcc, BufLen);
BufUsed = File->Read(Buf, BufLen);
bool Status = (Start >= BufPos) &&
((Start + Len) < (BufPos + BufUsed));
}
else
{
BufUsed = 0;
}
}
else
{
// In memory doc?
bool Status = true;
}
}
});
}
else
{
Status = (Start >= BufPos) && (Start + Len <= BufPos + BufUsed);
}
}
return Status;
}
bool LHexBuffer::GetLocationOfByte(LArray &Loc, int64 Offset, const char16 *LineBuf)
{
if (Offset < 0)
return false;
int64 X = Offset % View->BytesPerLine;
int64 Y = Offset / View->BytesPerLine;
int64 YPos = View->VScroll ? View->VScroll->Value() : 0;
int64 YPx = (Y - YPos) * View->CharSize.y;
int64 YOff = Y - YPos;
LAutoWString w;
int HexLen = (int)X * 3;
int AsciiLen = (int)((View->BytesPerLine * 3) + GAP_HEX_ASCII + X);
if (!LineBuf)
{
if (!Content.IdxCheck(YOff) || !Content[YOff])
{
return false;
}
if (!w.Reset(Utf8ToWide(Content[YOff])))
{
LAssert(!"Conversion failed");
return false;
}
LineBuf = w.Get();
}
{
LRect &rcHex = Loc.New();
LDisplayString ds(View->Font, LineBuf, (int)HexLen);
int x1 = ds.X();
LDisplayString ds2(View->Font, LineBuf, (int)HexLen+2);
int x2 = ds2.X();
rcHex.ZOff(x2 - x1 - 1, View->CharSize.y-1);
rcHex.Offset(x1 + Pos.x1, (int) (YPx + Pos.y1));
}
{
LRect &rcAscii = Loc.New();
LDisplayString ds(View->Font, LineBuf, (int)AsciiLen);
int x1 = ds.X();
LDisplayString ds2(View->Font, LineBuf, (int)AsciiLen+1);
int x2 = ds2.X();
rcAscii.ZOff(x2 - x1 - 1, View->CharSize.y-1);
rcAscii.Offset(x1 + Pos.x1, (int) (YPx + Pos.y1));
}
return true;
}
enum ColourFlags
{
ForeCol = 0,
BackCol = 1,
SelectedCol = 2,
ChangedCol = 4,
CursorCol = 8,
};
#define Int2Hex(c) ( (c) < 10 ? '0' + (c) : (c) - 10 + 'A' )
void LHexBuffer::OnPaint(LSurface *pDC, int64 Start, int64 Len, LHexBuffer *Compare)
{
// First position the layout
int64 BufOff = Start - BufPos;
int64 Bytes = MIN(Len, BufUsed - BufOff);
int Lines = (int)((Bytes + View->BytesPerLine - 1) / View->BytesPerLine);
// Colour setup
bool SelectedBuf = View->Cursor.Buf == this;
LColour WkSp = L_WORKSPACE;
float Mix = 0.85f;
LColour Colours[16];
// memset(&Colours, 0xaa, sizeof(Colours));
Colours[ForeCol] = LColour(L_TEXT);
Colours[BackCol] = LColour(L_WORKSPACE);
Colours[ForeCol | SelectedCol] = SelectedBuf ? ColourSelectionFore : LColour(L_TEXT);
Colours[BackCol | SelectedCol] = SelectedBuf ? ColourSelectionBack : LColour(ColourSelectionBack).Mix(WkSp, Mix);
Colours[ForeCol | ChangedCol] = LColour(L_TEXT);
Colours[BackCol | ChangedCol].Rgb(239, 203, 5);
Colours[ForeCol | ChangedCol | SelectedCol] = Colours[ForeCol | SelectedCol].Mix(Colours[ForeCol | ChangedCol]);
Colours[BackCol | ChangedCol | SelectedCol] = Colours[BackCol | SelectedCol].Mix(Colours[BackCol | ChangedCol]);
Colours[ForeCol | CursorCol] = LColour(L_TEXT);
Colours[BackCol | CursorCol].Rgb(192, 192, 192);
for (int i = 10; i < 16; i++)
{
LColour a = Colours[i - 8], b;
if (a.GetGray() > 0x80)
b = LColour::Black.Mix(a, 0.75);
else
b = LColour::White.Mix(a, 0.5);
Colours[i | CursorCol] = b;
}
#if 0
static bool First = true;
if (First)
{
First = false;
for (int i=0; i
\n",
i,
i & BackCol ? "Back" : "Fore",
i & SelectedCol ? "Selected" : "Unselected",
i & ChangedCol ? "Changed" : "Unchanged",
i & CursorCol ? "Cursor" : "NonCursor",
R24(Colours[i]),
G24(Colours[i]),
B24(Colours[i]));
}
}
#endif
// Now draw the layout data
char s[256] = {0};
uint8_t ForeFlags[256];
uint8_t BackFlags[256];
int EndY = Pos.y1 + (Lines * View->CharSize.y);
EndY = MAX(EndY, Pos.y1);
Content.Length(0);
for (int Line=0; LineCharSize.y);
int Ch = 0;
// This is relative to the start of the buffer.
int64 LineStart = BufOff + (Line * View->BytesPerLine);
// Absolute file position
int64 AbsPos = BufPos + LineStart;
// Setup comparison stuff
uint8_t *CompareBuf = NULL;
int CompareLen = 0;
if (Compare &&
Compare->GetData(AbsPos, View->BytesPerLine))
{
CompareBuf = Compare->Buf + (AbsPos - Compare->BufPos);
CompareLen = View->BytesPerLine;
}
else
{
CompareBuf = NULL;
CompareLen = 0;
}
// Clear the colours for this line
memset(&ForeFlags, ForeCol, sizeof(ForeFlags));
memset(&BackFlags, BackCol, sizeof(BackFlags));
// Print the hex bytes to the line
int64 n;
int64 FromStart = BufOff + (Line * View->BytesPerLine);
int64 From = FromStart, To = FromStart + View->BytesPerLine;
for (n=From; n> 4);
s[Ch++] = Int2Hex(Buf[n] & 0xf);
}
else
{
s[Ch++] = ' ';
s[Ch++] = ' ';
}
s[Ch++] = ' ';
}
// Separator between hex/ascii
Ch += sprintf_s(s + Ch, sizeof(s) - Ch, " ");
// Print the ascii characters to the line
char *p = s + Ch;
int StartOfAscii = Ch;
for (n=From; n= ' ' && c < 0x7f) ? c : '.';
}
else
{
*p++ = ' ';
}
}
*p++ = 0;
Content[Line] = s;
int64 CursorOff = -1;
if (View->Cursor.Buf == this)
{
if (View->Cursor.Offset >= BufPos &&
View->Cursor.Offset < BufPos + BufUsed)
{
CursorOff = View->Cursor.Offset - BufPos;
if ((CursorOff >= From) && (CursorOff < To))
CursorOff -= From;
else
CursorOff = -1;
}
}
// Draw text
LFont *Font = View->Font;
Font->Colour(L_TEXT, L_WORKSPACE);
char16 *Wide = (char16*)LNewConvertCp(LGI_WideCharset, s, "iso-8859-1");
if (Wide)
{
// Paint the selection into the colour buffers
int64 DocPos = BufPos + LineStart;
int64 Min = View->HasSelection() ? MIN(View->Selection.Offset, View->Cursor.Offset) : -1;
int64 Max = View->HasSelection() ? MAX(View->Selection.Offset, View->Cursor.Offset) : -1;
if (Min < DocPos + View->BytesPerLine &&
Max >= DocPos)
{
// Part or all of this line is selected
int64 s = ((View->Selection.Offset - DocPos) * 3) + View->Selection.Nibble;
int64 e = ((View->Cursor.Offset - DocPos) * 3) + View->Cursor.Nibble;
if (s > e)
{
int64 i = s;
s = e;
e = i;
}
if (s < 0)
s = 0;
if (e > View->BytesPerLine * 3 - 2)
e = View->BytesPerLine * 3 - 2;
for (int64 i=s; i<=e; i++)
{
ForeFlags[i] |= SelectedCol;
BackFlags[i] |= SelectedCol;
}
for (int64 i=(s/3)+StartOfAscii; i<=(e/3)+StartOfAscii; i++)
{
ForeFlags[i] |= SelectedCol;
BackFlags[i] |= SelectedCol;
}
}
// Colour the back of the cursor gray...
if (CursorOff >= 0 && /*View->Selection.Index < 0 && */View->Cursor.Flash)
{
BackFlags[(CursorOff * 3) + View->Cursor.Nibble] |= CursorCol;
BackFlags[StartOfAscii + CursorOff] |= CursorCol;
}
// Go through the colour buffers, painting in runs of similar colour
LRect r;
int CxF = Pos.x1 << LDisplayString::FShift;
auto Len = p - s;
for (int i=0; iColour(Colours[ForeFlags[i]], Colours[BackFlags[i]]);
Str.FDraw(pDC, CxF, CurY<> LDisplayString::FShift;
if (Cx < Pos.x2)
{
pDC->Colour(L_WORKSPACE);
pDC->Rectangle(Cx, CurY, Pos.x2, CurY+View->CharSize.y);
}
DeleteArray(Wide);
}
if (CursorOff >= 0)
{
// Draw cursor
GetLocationOfByte(View->Cursor.Pos, View->Cursor.Offset, Wide);
pDC->Colour(View->Focus() ? L_TEXT : L_LOW);
for (unsigned i=0; iCursor.Pos.Length(); i++)
{
LRect r = View->Cursor.Pos[i];
r.y1 = r.y2;
if (i == 0)
{
// Hex side..
if (View->Cursor.Nibble)
r.x1 += View->CharSize.x;
else
r.x2 -= View->CharSize.x;
if (View->Cursor.Pane == HexPane)
r.y1--;
}
else if (View->Cursor.Pane == AsciiPane)
{
r.y1--;
}
pDC->Rectangle(&r);
}
}
}
if (EndY < Pos.y2)
{
LRect r(Pos.x1, EndY, Pos.x2, Pos.y2);
pDC->Colour(L_WORKSPACE);
pDC->Rectangle(&r);
}
}
//////////////////////////////////////////////////////////////////////////////////////
LHexView::LHexView(AppWnd *app, IHexBar *bar)
{
// Init
App = app;
Bar = bar;
Font = 0;
CharSize.x = 8;
CharSize.y = 16;
IsHex = true;
BytesPerLine = 16;
IntWidth = 1;
SetId(IDC_HEX_VIEW);
// Font
LFontType Type;
if (Type.GetSystemFont("Fixed"))
{
Font = Type.Create();
if (Font)
{
LDisplayString ds(Font, "A");
CharSize.x = ds.X();
CharSize.y = ds.Y();
}
}
else LAssert(0);
Attach(App);
Name("LHexView");
SetScrollBars(false, true);
if (VScroll)
{
VScroll->SetNotify(this);
}
}
LHexView::~LHexView()
{
DeleteObj(Font);
Empty();
}
bool LHexView::Empty()
{
Buf.DeleteObjects();
Cursor.Empty();
Selection.Empty();
return true;
}
int LHexView::OnNotify(LViewI *c, LNotification n)
{
switch (c->GetId())
{
case IDC_VSCROLL:
{
Invalidate();
break;
}
}
return 0;
}
int64 LHexView::GetFileSize()
{
if (Buf.Length() && Buf[0])
return Buf[0]->Size;
return -1;
}
void LHexView::SetFileSize(int64 size)
{
// Save any changes
App->SetDirty(false, [this, size](auto ok)
{
if (ok &&
Buf.Length() &&
Cursor.Buf)
{
Cursor.Buf->SetSize((size_t)size);
auto p = Cursor.Buf->BufPos;
Cursor.Buf->BufPos++;
Cursor.Buf->GetData(p, 1);
UpdateScrollBar();
Invalidate();
}
});
}
void LHexView::SetIsHex(bool i)
{
if (i != IsHex)
{
IsHex = i;
Invalidate();
}
}
void LHexView::Copy(FormatType Fmt)
{
if (Buf.Length() == 0 || !Cursor.Buf)
{
LgiMsg(this, "Error: No buffer to copy.", AppName);
return;
}
LClipBoard c(this);
LHexBuffer *b = Cursor.Buf;
int64 Min, Max;
if (HasSelection())
{
Min = MIN(Selection.Offset, Cursor.Offset);
Max = MAX(Selection.Offset, Cursor.Offset);
}
else
{
Min = 0;
Max = b->Size - 1;
}
int64 Len = Max - Min + 1;
if (!b->GetData(Min, (size_t)Len))
{
LgiMsg(this, "Error: Failed to get source buffer.", AppName);
return;
}
uint64 Offset = Min - b->BufPos;
uchar *Ptr = b->Buf + Offset;
if (Len > b->BufUsed - Offset)
{
Len = b->BufUsed - Offset;
}
LStringPipe p;
if (Fmt == FmtCode)
p.Print("unsigned char Var[] = {\n\t");
int64 i;
for (i=0; i Ptr;
ssize_t Len = 0;
char *Txt;
#ifdef WIN32
if (c.Binary(CF_PRIVATEFIRST, Ptr, &Len))
{
}
else
#endif
{
Txt = c.Text();
if (!Txt)
return;
if (Fmt == FmtHex)
{
// Convert from binary...
LArray Out;
int High = -1;
bool HasComma = false;
for (char *i = Txt; *i; i++)
{
if (*i == ',')
{
HasComma = true;
break;
}
}
if (HasComma)
{
// Comma separated integers?
for (char *i = Txt; *i; )
{
while (*i && !IsDigit(*i))
i++;
char *e = i;
while (*e && IsDigit(*e))
e++;
Out.Add(Atoi(i));
i = e;
}
}
else
{
// Hex data?
for (char *i = Txt; *i; i++)
{
int n = -1;
if (*i >= '0' && *i <= '9')
n = *i - '0';
else if (*i >= 'a' && *i <= 'f')
n = *i - 'a' + 10;
else if (*i >= 'A' && *i <= 'F')
n = *i - 'A' + 10;
if (n >= 0)
{
if (High >= 0)
{
Out.Add(High << 4 | n);
High = -1;
}
else
{
High = n;
}
}
}
}
if (Out.Length())
{
Len = Out.Length();
Ptr.Reset(Out.Release());
}
}
else
{
Len = strlen(Txt);
Ptr.Reset((uint8_t*)NewStr(Txt));
}
}
if (Ptr && Len > 0)
{
Cursor.Offset = MAX(0, Cursor.Offset);
if (Buf.Length() == 0 || !Buf[0]->GetData(Cursor.Offset, Len))
{
if (!CreateFile(Len))
return;
if (!Buf[0]->GetData(0, Len))
return;
}
LHexBuffer *b = Buf[0];
if (b)
{
memcpy(b->Buf + Cursor.Offset - b->BufPos, Ptr, Len);
b->SetDirty();
App->SetDirty(true, [this](auto ok)
{
Invalidate();
DoInfo();
});
}
}
}
void LHexView::UpdateScrollBar()
{
int Lines = GetClient().Y() / CharSize.y;
int64 DocLines = 0;
for (unsigned i=0; iSize + 15) / 16;
DocLines = MAX(DocLines, BufLines);
}
SetScrollBars(false, DocLines > Lines);
if (VScroll)
{
VScroll->SetRange(DocLines > 0 ? DocLines : 0);
VScroll->SetPage(Lines);
}
}
void LHexView::SwapBytes(void *p, int Len)
{
if (Bar && !Bar->IsLittleEndian())
{
uchar *c = (uchar*)p;
for (int i=0; i>1; i++)
{
uchar t = c[i];
c[i] = c[Len-1-i];
c[Len-1-i] = t;
}
}
}
bool LHexView::GetDataAtCursor(char *&Data, size_t &Len)
{
LHexBuffer *b = Buf.Length() ? Buf.First() : NULL;
if (b && b->Buf)
{
size_t Offset = (size_t)(Cursor.Offset - b->BufPos);
Data = (char*)b->Buf + Offset;
Len = MIN(b->BufUsed, b->BufLen) - Offset;
return true;
}
return false;
}
bool LHexView::HasSelection()
{
return Selection.Offset >= 0;
}
int64 LHexView::GetSelectedNibbles()
{
if (!HasSelection())
return 0;
auto c = (Cursor.Offset << 1) + Cursor.Nibble;
auto s = (Selection.Offset << 1) + Selection.Nibble;
int64 Min, Max;
if (s < c)
{
Min = s;
Max = c;
}
else
{
Max = s;
Min = c;
}
return Max - Min + 1;
}
void LHexView::InvalidateLines(LArray &NewLoc, LArray &OldLoc)
{
if (NewLoc.Length() > 0 && OldLoc.Length() > 0)
{
// Work out the union of NewLoc and OldLoc
int MinY = MIN(NewLoc[0].y1, OldLoc[0].y1);
int MaxY = MAX(NewLoc[0].y2, OldLoc[0].y2);
LRect u(0, MinY, X()-1, MaxY);
Invalidate(&u);
}
else
{
Invalidate();
}
}
LHexBuffer *LHexView::GetCursorBuffer()
{
return Cursor.Buf;
}
void LHexView::SetCursor(LHexBuffer *b, int64 cursor, int nibble, bool Selecting)
{
LArray OldLoc, NewLoc;
bool SelectionChanging = false;
bool SelectionEnding = false;
if (!b)
b = Cursor.Buf;
if (!b)
return;
if (Selecting)
{
if (!HasSelection())
// Start selection
Selection = Cursor;
SelectionChanging = true;
}
else
{
if (HasSelection())
{
// Deselecting
// Repaint the entire selection area...
b->GetLocationOfByte(NewLoc, Cursor.Offset, NULL);
b->GetLocationOfByte(OldLoc, Selection.Offset, NULL);
InvalidateLines(NewLoc, OldLoc);
SelectionEnding = true;
Selection.Offset = -1;
}
}
if (!SelectionEnding)
b->GetLocationOfByte(OldLoc, Cursor.Offset, NULL);
// else the selection just ended and the old cursor location just got repainted anyway
// Limit to doc
if (cursor >= b->Size)
{
cursor = b->Size - 1;
nibble = 1;
}
if (cursor < 0)
{
cursor = 0;
nibble = 0;
}
// Is different?
if (Cursor.Buf != b ||
Cursor.Offset != cursor ||
Cursor.Nibble != nibble)
{
// Set the cursor
Cursor.Buf = b;
Cursor.Offset = cursor;
Cursor.Nibble = nibble;
// Make sure the cursor is in the viewable area?
if (VScroll)
{
int64 Start = (uint64) (VScroll ? VScroll->Value() : 0) * 16;
int Lines = GetClient().Y() / CharSize.y;
int64 End = MIN(b->Size, Start + (Lines * 16));
if (Cursor.Offset < Start)
{
// Scroll up
VScroll->Value((Cursor.Offset - (Cursor.Offset%16)) / 16);
Invalidate();
}
else if (Cursor.Offset >= End)
{
// Scroll down
int64 NewVal = (Cursor.Offset - (Cursor.Offset%16) - ((Lines-1) * 16)) / 16;
VScroll->Value(NewVal);
Invalidate();
}
}
if (Bar)
{
Bar->SetOffset(Cursor.Offset);
DoInfo();
}
Cursor.Flash = true;
if (b->GetLocationOfByte(NewLoc, Cursor.Offset, NULL))
{
if (!SelectionChanging)
{
// Just update the cursor's old and new locations
NewLoc.Add(OldLoc);
// DbgRect = NewLoc;
for (unsigned i=0; iGetLocationOfByte(NewLoc, Cursor.Offset, NULL);
InvalidateLines(NewLoc, OldLoc);
}
SendNotify(LNotifyCursorChanged);
}
int64 LHexView::Search(SearchDlg *For, uchar *Bytes, size_t Len)
{
int64 Hit = -1;
if (For->Bin && For->Length > 0)
{
for (int i=0; iLength; i++)
{
bool Match = true;
for (int n=0; nLength; n++)
{
if (For->MatchCase || For->ForHex)
{
if (For->Bin[n] != Bytes[i+n])
{
Match = false;
break;
}
}
else
{
if (tolower(For->Bin[n]) != tolower(Bytes[i+n]))
{
Match = false;
break;
}
}
}
if (Match)
{
Hit = i;
break;
}
}
}
return Hit;
}
void LHexView::DoSearch(SearchDlg *For)
{
size_t Block = 32 << 10;
int64 Hit = -1, c;
int64 Time = LCurrentTime();
LProgressDlg *Prog = 0;
LHexBuffer *b = Cursor.Buf;
if (!b)
return;
// Search through to the end of the file...
for (c = Cursor.Offset + 1; c < b->Size; c += Block)
{
size_t Actual = (size_t)MIN(Block, GetFileSize() - c);
if (b->GetData(c, Actual))
{
Hit = Search(For, b->Buf + (c - b->BufPos), Actual);
if (Hit >= 0)
{
Hit += c;
break;
}
int64 Now = LCurrentTime();
if (Now - Time > UI_UPDATE_SPEED)
{
Time = Now;
if (!Prog)
{
if ((Prog = new LProgressDlg(this)))
{
Prog->SetDescription("Searching...");
Prog->SetRange(GetFileSize());
Prog->SetScale(1.0 / 1024.0);
Prog->SetType("kb");
}
}
else
{
Prog->Value(c - Cursor.Offset);
// LYield();
}
}
}
else break;
}
if (Hit < 0)
{
// Now search from the start of the file to the original cursor
for (c = 0; c < Cursor.Offset; c += Block)
{
if (b->GetData(c, Block))
{
size_t Actual = (size_t)MIN(Block, Cursor.Offset - c);
Hit = Search(For, b->Buf + (c - b->BufPos), Actual);
if (Hit >= 0)
{
Hit += c;
break;
}
int64 Now = LCurrentTime();
if (Now - Time > UI_UPDATE_SPEED)
{
Time = Now;
if (!Prog)
{
if ((Prog = new LProgressDlg(this)))
{
Prog->SetDescription("Searching...");
Prog->SetRange(GetFileSize());
Prog->SetScale(1.0 / 1024.0);
Prog->SetType("kb");
}
}
else
{
Prog->Value(b->Size - Cursor.Offset + c);
// LYield();
}
}
}
else break;
}
}
if (Hit >= 0)
{
SetCursor(b, Hit);
SetCursor(b, Hit + For->Length - 1, 1, true);
}
DeleteObj(Prog);
}
void LHexView::SetBit(uint8_t Bit, bool On)
{
LHexBuffer *b = Cursor.Buf;
if (!b)
return;
if (b->GetData(Cursor.Offset, 1))
{
if (On)
{
b->Buf[Cursor.Offset - b->BufPos] |= Bit;
}
else
{
b->Buf[Cursor.Offset - b->BufPos] &= ~Bit;
}
App->SetDirty(true, [this](auto ok)
{
Invalidate();
DoInfo();
});
}
}
void LHexView::SetByte(uint8_t Byte)
{
LHexBuffer *b = Cursor.Buf;
if (!b)
return;
if (b->GetData(Cursor.Offset, 1))
{
if (b->Buf[Cursor.Offset - b->BufPos] != Byte)
{
b->Buf[Cursor.Offset - b->BufPos] = Byte;
App->SetDirty(true, [this](bool ok)
{
Invalidate();
DoInfo();
});
}
}
}
void LHexView::SetShort(uint16 Short)
{
LHexBuffer *b = Cursor.Buf;
if (!b)
return;
if (b->GetData(Cursor.Offset, 2))
{
SwapBytes(&Short, sizeof(Short));
uint16 *p = (uint16*) (&b->Buf[Cursor.Offset - b->BufPos]);
if (*p != Short)
{
*p = Short;
App->SetDirty(true, [this](auto ok)
{
Invalidate();
DoInfo();
});
}
}
}
void LHexView::SetInt(uint32_t Int)
{
LHexBuffer *b = Cursor.Buf;
if (!b)
return;
if (b->GetData(Cursor.Offset, 4))
{
SwapBytes(&Int, sizeof(Int));
uint32_t *p = (uint32_t*) (&b->Buf[Cursor.Offset - b->BufPos]);
if (*p != Int)
{
*p = Int;
App->SetDirty(true, [this](auto ok)
{
Invalidate();
DoInfo();
});
}
}
}
void LHexView::DoInfo()
{
LHexBuffer *b = Cursor.Buf;
if (Bar && b)
{
bool IsSigned = Bar->IsSigned();
LView *w = GetWindow();
char s[256] = "";
if (b->GetData(Cursor.Offset, 1))
{
int c = b->Buf[Cursor.Offset - b->BufPos], sc;
if (IsSigned)
sc = (char)b->Buf[Cursor.Offset - b->BufPos];
else
sc = b->Buf[Cursor.Offset - b->BufPos];
Bar->NotifyOff = true;
sprintf(s, "%i", sc);
w->SetCtrlName(IDC_DEC_1, s);
sprintf(s, "%02.2X", c);
w->SetCtrlName(IDC_HEX_1, s);
sprintf(s, "%c", c >= ' ' && c <= 0x7f ? c : '.');
w->SetCtrlName(IDC_ASC_1, s);
uint8_t Bits = b->Buf[Cursor.Offset - b->BufPos];
Bar->SetCtrlValue(IDC_BIT7, (Bits & 0x80) != 0);
Bar->SetCtrlValue(IDC_BIT6, (Bits & 0x40) != 0);
Bar->SetCtrlValue(IDC_BIT5, (Bits & 0x20) != 0);
Bar->SetCtrlValue(IDC_BIT4, (Bits & 0x10) != 0);
Bar->SetCtrlValue(IDC_BIT3, (Bits & 0x08) != 0);
Bar->SetCtrlValue(IDC_BIT2, (Bits & 0x04) != 0);
Bar->SetCtrlValue(IDC_BIT1, (Bits & 0x02) != 0);
Bar->SetCtrlValue(IDC_BIT0, (Bits & 0x01) != 0);
Bar->NotifyOff = false;
}
LViewI *Hex, *Dec;
if (w->GetViewById(IDC_HEX_2, Hex) &&
w->GetViewById(IDC_DEC_2, Dec))
{
bool Valid = b->GetData(Cursor.Offset, 2);
LString sHex, sDec;
if (Valid)
{
uint16 *sp = (uint16*)(b->Buf+(Cursor.Offset - b->BufPos));
uint16 c = *sp;
SwapBytes(&c, sizeof(c));
int c2 = (int16)c;
sDec.Printf("%i", IsSigned ? c2 : c);
sHex.Printf("%04.4X", c);
}
Dec->Name(Valid ? sDec : NULL);
Hex->Name(Valid ? sHex : NULL);
Dec->Enabled(Valid);
Hex->Enabled(Valid);
}
if (w->GetViewById(IDC_HEX_4, Hex) &&
w->GetViewById(IDC_DEC_4, Dec))
{
bool Valid = b->GetData(Cursor.Offset, 4);
LString sHex, sDec;
if (Valid)
{
uint32_t *lp = (uint32_t*)(b->Buf + (Cursor.Offset - b->BufPos));
uint32_t c = *lp;
SwapBytes(&c, sizeof(c));
sDec.Printf(IsSigned ? "%i" : "%u", c);
sHex.Printf("%08.8X", c);
}
Dec->Name(Valid ? sDec : NULL);
Hex->Name(Valid ? sHex : NULL);
Dec->Enabled(Valid);
Hex->Enabled(Valid);
}
}
}
bool FileToArray(LArray &a, const char *File)
{
LFile f;
if (!f.Open(File, O_READ))
return false;
if (!a.Length((size_t)f.GetSize()))
return false;
auto rd = f.Read(&a[0], a.Length());
if (rd != a.Length())
return false;
return true;
}
void LHexView::CompareFile(const char *CmpFile)
{
if (LFileExists(CmpFile))
{
LAutoPtr b(new LHexBuffer(this));
if (b)
{
if (b->Open(CmpFile, false))
{
Buf.Add(b.Release());
UpdateScrollBar();
Invalidate();
}
}
}
}
bool LHexView::CreateFile(int64 Len)
{
App->SetDirty(false, [this, Len](auto ok)
{
if (!ok)
return;
LHexBuffer *b = new LHexBuffer(this);
if (!b)
return;
Buf.Add(b);
b->Buf = new uchar[b->BufLen = (size_t)Len];
if (!b->Buf)
{
Buf.DeleteObjects();
return;
}
memset(b->Buf, 0, (size_t)Len);
b->BufUsed = (size_t)Len;
b->Size = Len;
Focus(true);
SetCursor(b, 0);
UpdateScrollBar();
if (Bar)
Bar->SetOffset(0);
Invalidate();
DoInfo();
App->Name(AppName);
App->OnDocument(true);
});
return true;
}
-bool LHexView::OpenFile(const char *FileName, bool ReadOnly)
+void LHexView::OpenFile(const char *FileName, bool ReadOnly, std::function Callback)
{
- bool Status = false;
-
- // FIXME: Doesn't return a valid status... convert to callback?
- App->SetDirty(false, [this, FileName=LString(FileName), ReadOnly](auto ok)
+ App->SetDirty(false, [this, FileName=LString(FileName), ReadOnly, Callback](auto ok)
{
if (!ok)
+ {
+ if (Callback) Callback(false);
return;
+ }
Empty();
LAutoPtr b(new LHexBuffer(this));
if (b && FileName)
{
if (b->Open(FileName, ReadOnly))
{
Focus(true);
SetCursor(b, 0);
Buf[0] = b.Release();
- bool Status = true;
}
else
{
LgiMsg(this, "Couldn't open '%s' for reading.", AppName, MB_OK, FileName);
+ if (Callback)
+ Callback(false);
+ return;
}
}
if (Bar)
{
Bar->SetOffset(0);
}
Invalidate();
DoInfo();
char Title[MAX_PATH_LEN + 100];
sprintf_s(Title, sizeof(Title), "%s [%s]", AppName, FileName.Get());
App->Name(Title);
UpdateScrollBar();
+ if (Callback)
+ Callback(true);
});
-
- return Status;
}
bool LHexView::CloseFile(ssize_t Index)
{
if (Index < 0)
Index = Buf.Length() - 1;
if (!Buf.AddressOf((unsigned) Index))
return false;
delete Buf[Index];
Buf.DeleteAt(Index, true);
Cursor.Empty();
Selection.Empty();
Invalidate();
return true;
}
bool LHexView::IsDirty()
{
for (auto b : Buf)
{
if (b->IsDirty)
return true;
}
return false;
}
enum Msgs
{
M_PROCESS_SAVE = M_USER,
};
struct SaveState : public LView::ViewEventTarget
{
bool status = true;
LHexView *view = NULL;
LArray work;
std::function finished;
SaveState(LHexView *v) : LView::ViewEventTarget(v, M_PROCESS_SAVE)
{
view = v;
}
~SaveState()
{
LAssert(work.Length() == 0);
}
void Start()
{
PostEvent(M_PROCESS_SAVE);
}
void SaveBuffer(LHexBuffer *b)
{
b->Save();
b->SetDirty(false);
}
LMessage::Result OnEvent(LMessage *Msg)
{
if (Msg->Msg() != M_PROCESS_SAVE)
return 0;
if (work.Length() == 0)
{
finished(status);
delete this;
return 0;
}
auto b = work[0];
work.DeleteAt(0);
if (b->IsDirty || !b->File)
{
if (!b->File)
{
auto s = new LFileSelect;
s->Parent(view);
s->Save([this, b](auto s, auto code)
{
if (code)
{
b->File = new LFile;
if (b->File && !b->File->Open(s->Name(), O_READWRITE))
DeleteObj(b->File);
view->GetApp()->SetCurFile(s->Name());
SaveBuffer(b);
}
else
{
status = false;
}
delete s;
});
}
SaveBuffer(b);
}
return 0;
}
};
void LHexView::Save(std::function callback)
{
// Save all buffers
auto ss = new SaveState(this);
ss->work = Buf;
ss->finished = callback;
ss->Start();
}
bool LHexView::SaveFile(LHexBuffer *b, const char *FileName)
{
bool Status = false;
if (!b)
b = Cursor.Buf;
if (b &&
b->File &&
FileName)
{
if (stricmp(FileName, b->File->GetName()) == 0)
{
if (b->File->Seek(b->BufPos, SEEK_SET) == b->BufPos)
{
size_t Len = (size_t)MIN(b->BufLen, b->Size - b->BufPos);
Status = b->File->Write(b->Buf, Len) == Len;
}
}
}
return Status;
}
bool LHexView::HasFile()
{
if (Buf.Length() && Buf[0])
return Buf.First()->File != NULL;
return false;
}
void LHexView::SaveSelection(LHexBuffer *b, const char *FileName)
{
if (!b)
b = Cursor.Buf;
if (b &&
b->HasData() &&
FileName)
{
LFile f;
if (HasSelection() &&
f.Open(FileName, O_WRITE))
{
int64 Min = MIN(Selection.Offset, Cursor.Offset);
int64 Max = MAX(Selection.Offset, Cursor.Offset);
int64 Len = Max - Min + 1;
f.SetSize(Len);
f.SetPos(0);
int64 Block = 4 << 10;
for (int64 i=0; iGetData(AbsPos, Bytes))
{
uchar *p = b->Buf + (AbsPos - b->BufPos);
f.Write(p, Bytes);
}
}
}
}
}
void LHexView::SelectAll()
{
LHexBuffer *b = Cursor.Buf;
if (b)
{
SetCursor(b, 0, 0, false);
SetCursor(b, b->Size-1, 1, true);
}
}
void LHexView::SelectionFillRandom(LStream *Rnd)
{
if (!Rnd || !Cursor.Buf)
return;
LHexBuffer *b = Cursor.Buf;
int64 Min = MIN(Selection.Offset, Cursor.Offset);
int64 Max = MAX(Selection.Offset, Cursor.Offset);
int64 Len = Max - Min + 1;
if (b->File)
{
int64 Last = LCurrentTime();
int64 Start = Last;
LProgressDlg Dlg(App);
LArray Buf;
Buf.Length(2 << 20);
Dlg.SetRange(Len);
Dlg.SetScale(1.0 / 1024.0 / 1024.0);
Dlg.SetType("MB");
b->File->SetPos(Min);
#if 1
if (Rnd->Read(&Buf[0], Buf.Length()) != Buf.Length())
{
LgiMsg(this, "Random stream failed.", AppName);
return;
}
#endif
for (int64 i=0; !Dlg.IsCancelled() && iRead(&Buf[0], Remain) != Remain)
{
LgiMsg(this, "Random stream failed.", AppName);
return;
}
#endif
ssize_t w = b->File->Write(&Buf[0], (size_t)Remain);
if (w != Remain)
{
LgiMsg(this, "Write file failed.", AppName);
break;
}
int64 Now = LCurrentTime();
if (Now - Last > 500)
{
Dlg.Value(i);
// LYield();
Last = Now;
double Sec = (double)(int64)(Now - Start) / 1000.0;
double Rate = (double)(int64)(i + Remain) / Sec;
int TotalSeconds = (int) ((Len - i - Remain) / Rate);
char s[64];
sprintf(s, "%i:%02.2i:%02.2i remaining", TotalSeconds/3600, (TotalSeconds%3600)/60, TotalSeconds%60);
Dlg.SetDescription(s);
}
}
if (b->File->SetPos(b->BufPos) == b->BufPos)
{
b->BufUsed = b->File->Read(b->Buf, b->BufLen);
}
Invalidate();
}
}
bool LHexView::Pour(LRegion &r)
{
LRect *Best = FindLargest(r);
if (Best)
{
SetPos(*Best, true);
return true;
}
return false;
}
void LHexView::OnPosChange()
{
UpdateScrollBar();
LLayout::OnPosChange();
}
void LHexView::InvalidateCursor()
{
for (int i=0; i= 500)
{
Cursor.Flash = !Cursor.Flash;
Cursor.FlashTs = Now;
InvalidateCursor();
}
if (IsCapturing())
{
LMouse m;
if (GetMouse(m))
{
LRect c = GetClient();
if (!c.Overlap(m.x, m.y))
{
OnMouseMove(m);
}
}
}
}
void LHexView::OnPaint(LSurface *pDC)
{
LRect Cli = GetClient();
LRect r = Cli;
#if DEBUG_COVERAGE_CHECK
pDC->Colour(LColour(255, 0, 255));
pDC->Rectangle();
#endif
LRegion TopMargin;
if (Buf.Length() > 1)
{
r.y1 += (int) (LSysBold->GetHeight() * 1.5);
TopMargin = LRect(0, 0, r.x2, r.y1-1);
}
int64 YPos = VScroll ? VScroll->Value() : 0;
int64 Start = YPos * BytesPerLine;
int Columns = (3 * BytesPerLine) + GAP_HEX_ASCII + (BytesPerLine);
int Lines = (r.Y() + CharSize.y -1) / CharSize.y;
Cursor.Pos.Length(0);
int64 MaxSize = 0;
for (unsigned int BufIdx = 0; BufIdx < Buf.Length(); BufIdx++)
MaxSize = MAX(MaxSize, Buf[BufIdx]->Size);
int64 AddrLines = (MaxSize + BytesPerLine - 1) / BytesPerLine;
int64 Addrs = AddrLines - YPos;
// Draw all the addresses
Font->Transparent(false);
Font->Colour(L_TEXT, L_WORKSPACE);
int CurrentY = 0;
int CurrentX = 0;
for (int Line=0; Line r.y2)
break;
int64 LineAddr = Start + (Line * BytesPerLine);
LString p;
if (IsHex)
p.Printf("%02.2x:%08.8X ", (uint)(LineAddr >> 32), (uint)LineAddr);
else
#ifdef WIN32
p.Printf("%11.11I64i ", LineAddr);
#else
p.Printf("%11.11lli ", LineAddr);
#endif
LDisplayString ds(Font, p);
ds.Draw(pDC, r.x1, CurrentY);
CurrentX = ds.X();
}
if (Addrs)
CurrentY += CharSize.y;
if (CurrentY < r.y2)
{
pDC->Colour(L_WORKSPACE);
pDC->Rectangle(r.x1, CurrentY, CurrentX-1, r.y2);
}
// Draw all the data buffers...
for (unsigned int BufIdx = 0; BufIdx < Buf.Length(); BufIdx++)
{
LHexBuffer *b = Buf[BufIdx];
b->Pos.ZOff(Columns * CharSize.x, Lines * CharSize.y);
b->Pos.Offset(r.x1 + (HEX_COLUMN * CharSize.x), r.y1);
if (BufIdx)
b->Pos.Offset
(
BufIdx
*
(Columns + GAP_FILES)
*
CharSize.x,
0
);
if (CurrentX < b->Pos.x1)
{
// Paint any whitespace before this column
pDC->Colour(L_WORKSPACE);
pDC->Rectangle(CurrentX + 1, r.y1, b->Pos.x1 - 1, r.y2);
}
if (Buf.Length() > 1)
{
LSysBold->Transparent(false);
LSysBold->Colour(L_TEXT, L_WORKSPACE);
LDisplayString Ds(LSysBold, b->File ? b->File->GetName() : LLoadString(IDS_UNTITLED_BUFFER));
LRect r(b->Pos.x1, 0, b->Pos.x1+Ds.X()-1, Ds.Y()-1);
Ds.Draw(pDC, r.x1, r.y1);
TopMargin.Subtract(&r);
}
int64 End = MIN(b->Size, Start + (Lines * BytesPerLine));
if (b->GetData(Start, (size_t)(End-Start)))
{
LHexBuffer *Comp = Buf.Length() > 1 ? Buf[!BufIdx] : NULL;
b->OnPaint(pDC, Start, End - Start, Comp);
}
CurrentX = b->Pos.x2;
}
if (CurrentX < r.x2)
{
// Paint any whitespace after the last column
pDC->Colour(L_WORKSPACE);
pDC->Rectangle(CurrentX, r.y1, r.x2, r.y2);
}
if (TopMargin.Length() > 0)
{
for (unsigned i=0; iColour(L_WORKSPACE);
pDC->Rectangle(r);
}
}
}
bool LHexView::OnMouseWheel(double Lines)
{
if (VScroll)
{
VScroll->Value(VScroll->Value() + (int)Lines);
Invalidate();
}
return true;
}
bool LHexView::GetCursorFromLoc(int x, int y, GHexCursor &c)
{
uint64 Start = ((uint64)(VScroll ? VScroll->Value() : 0)) * BytesPerLine;
int HexCols = BytesPerLine * 3;
int AsciiCols = HexCols + GAP_HEX_ASCII;
for (unsigned i=0; i= b->Pos.x1 && x <= b->Pos.x2)
{
int row = (y - b->Pos.y1) / CharSize.y;
auto Row = b->Content[row];
if (!Row)
Row = b->Content.First();
if (Row)
{
LDisplayString Ds(Font, b->Content[row]);
auto col = Ds.CharAt(x - b->Pos.x1);
if (col >= 0 && col < HexCols)
{
auto Byte = col / 3;
int Bit = col % 3;
c.Buf = b;
c.Offset = Start + (row * BytesPerLine) + Byte;
c.Nibble = Bit > 0;
c.Pane = HexPane;
c.BufIndex = i;
return true;
}
else if (col >= AsciiCols)
{
auto Asc = col - AsciiCols;
if (Asc < BytesPerLine)
{
c.Buf = b;
c.Offset = Start + (row * BytesPerLine) + Asc;
c.Nibble = 0;
c.Pane = AsciiPane;
c.BufIndex = i;
return true;
}
}
}
else
{
LAssert(!"No content?");
return false;
}
}
}
return false;
}
void LHexView::OnMouseClick(LMouse &m)
{
Capture(m.Down());
if (m.Down())
{
Focus(true);
if (m.Left())
{
GHexCursor c;
if (GetCursorFromLoc(m.x, m.y, c))
{
SetCursor(c.Buf, c.Offset, c.Nibble, m.Shift());
Cursor.Pane = c.Pane;
}
}
}
}
void LHexView::OnMouseMove(LMouse &m)
{
if (IsCapturing())
{
GHexCursor c;
if (GetCursorFromLoc(m.x, m.y, c))
{
if (c.Pane == AsciiPane &&
c.Offset >= Cursor.Offset)
{
c.Nibble = 1;
}
SetCursor(c.Buf, c.Offset, c.Nibble, true);
}
}
}
void LHexView::OnFocus(bool f)
{
Invalidate();
}
void LHexView::InvalidateByte(int64 Idx)
{
for (unsigned i=0; i Loc;
if (b->GetLocationOfByte(Loc, Idx, NULL))
{
Loc[0].x2 += CharSize.x;
for (unsigned i=0; iIsReadOnly)
{
if (k.Down())
{
if (Cursor.Pane == HexPane)
{
int c = -1;
if (k.c16 >= '0' && k.c16 <= '9') c = k.c16 - '0';
else if (k.c16 >= 'a' && k.c16 <= 'f') c = k.c16 - 'a' + 10;
else if (k.c16 >= 'A' && k.c16 <= 'F') c = k.c16 - 'A' + 10;
if (c >= 0 && c < 16)
{
uchar *Byte = b->Buf + (Cursor.Offset - b->BufPos);
if (Cursor.Nibble)
*Byte = (*Byte & 0xf0) | c;
else
*Byte = (c << 4) | (*Byte & 0xf);
b->SetDirty();
InvalidateByte(Cursor.Offset);
if (Cursor.Nibble == 0)
SetCursor(b, Cursor.Offset, 1);
else if (Cursor.Offset < b->Size - 1)
SetCursor(b, Cursor.Offset+1, 0);
}
}
else if (Cursor.Pane == AsciiPane)
{
uchar *Byte = b->Buf + (Cursor.Offset - b->BufPos);
*Byte = k.c16;
InvalidateByte(Cursor.Offset);
b->SetDirty();
SetCursor(b, Cursor.Offset + 1);
}
}
return true;
}
break;
}
case LK_RIGHT:
{
if (b && k.Down())
{
if (Cursor.Pane == HexPane)
{
if (Cursor.Nibble == 0)
SetCursor(b, Cursor.Offset, 1, k.Shift());
else if (Cursor.Offset < b->Size - 1)
SetCursor(b, Cursor.Offset + 1, 0, k.Shift());
}
else
{
SetCursor(b, Cursor.Offset + 1, 0);
}
}
return true;
break;
}
case LK_LEFT:
{
if (b && k.Down())
{
if (Cursor.Pane == HexPane)
{
if (Cursor.Nibble == 1)
SetCursor(b, Cursor.Offset, 0, k.Shift());
else if (Cursor.Offset > 0)
SetCursor(b, Cursor.Offset - 1, 1, k.Shift());
}
else
{
SetCursor(b, Cursor.Offset - 1, 0);
}
}
return true;
break;
}
case LK_UP:
{
if (b && k.Down())
{
SetCursor(b, Cursor.Offset - 16, Cursor.Nibble, k.Shift());
}
return true;
break;
}
case LK_DOWN:
{
if (b && k.Down())
{
if (k.Ctrl())
{
// Find next difference
bool Done = false;
/*
for (int64 n = Cursor - BufPos + 1; !Done && n < Size; n += Block)
{
if (GetData(n, Block))
{
int Off = n - BufPos;
int Len = BufUsed - Off;
if (Len > Block) Len = Block;
for (int i=0; iSize - 1, 1, k.Shift());
else
SetCursor(b, Cursor.Offset - (Cursor.Offset % 16) + 15, 1, k.Shift());
}
return true;
break;
}
case LK_BACKSPACE:
{
if (b && k.Down() && !k.IsChar)
{
if (Cursor.Pane == HexPane)
{
if (Cursor.Nibble == 0)
SetCursor(b, Cursor.Offset - 1, 1);
else
SetCursor(b, Cursor.Offset, 0);
}
else
{
SetCursor(b, Cursor.Offset - 1);
}
}
return true;
break;
}
case '\t':
{
if (k.Down())
{
if (k.IsChar)
{
if (Cursor.Pane == HexPane)
Cursor.Pane = AsciiPane;
else
Cursor.Pane = HexPane;
Invalidate();
}
}
return true;
break;
}
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////
AppWnd::AppWnd() : LDocApp(AppName,
#ifdef _WIN32
IDI_ICON1
#else
"Resources/icon64.png"
#endif
)
{
#ifdef MAC
LgiGetResObj(false, "ihex");
#endif
if (_Create())
{
DropTarget(true);
if (_LoadMenu("IDM_MENU", NULL, IDM_FILE_MENU, IDM_RECENT_MENU))
{
auto i = Menu->FindItem(IDM_SAVEAS);
DeleteObj(i);
CmdSave.MenuItem = Menu->FindItem(IDM_SAVE);
CmdClose.MenuItem = Menu->FindItem(IDM_CLOSE);
CmdChangeSize.MenuItem = Menu->FindItem(IDM_CHANGE_SIZE);
CmdFind.MenuItem = Menu->FindItem(IDM_SEARCH);
CmdNext.MenuItem = Menu->FindItem(IDM_NEXT);
}
Tools = LgiLoadToolbar(this, "Tools.gif", 24, 24);
if (Tools)
{
Tools->TextLabels(true);
Tools->Attach(this);
Tools->AppendButton("Open", IDM_OPEN);
CmdSave.ToolButton = Tools->AppendButton("Save", IDM_SAVE, TBT_PUSH, false);
// CmdSaveAs.ToolButton = Tools->AppendButton("Save As", IDM_SAVEAS, TBT_PUSH, false);
CmdFind.ToolButton = Tools->AppendButton("Search", IDM_SEARCH, TBT_PUSH, false, 3);
Tools->AppendSeparator();
CmdVisualise.ToolButton = Tools->AppendButton("Visualise", IDM_VISUALISE, TBT_TOGGLE, false, 4);
CmdText.ToolButton = Tools->AppendButton("Text", IDM_TEXTVIEW, TBT_TOGGLE, false, 5);
LRegion r(GetClient());
Tools->Pour(r);
}
PourAll();
int By = (int) (LSysFont->GetHeight() * 3.25);
Bar = new IHexBar(this, MAX(Tools ? Tools->Y() : 20, By));
Status = new LStatusBar;
if (Status)
{
StatusInfo[0] = Status->AppendPane("", -1);
StatusInfo[1] = Status->AppendPane("", 200);
if (StatusInfo[1]) StatusInfo[1]->Sunken(true);
Status->Attach(this);
}
Doc = new LHexView(this, Bar);
if (Bar && Doc)
{
Bar->View = Doc;
Bar->View->SetIsHex(Bar->IsHex());
}
OnDirty(false);
#ifndef WINDOWS
SetIcon("icon64.png");
#endif
Visible(true);
PourAll();
DropTarget(true);
}
}
AppWnd::~AppWnd()
{
DeleteObj(SearchDlg::Inst);
LAppInst->AppWnd = 0;
DeleteObj(Bar);
_Destroy();
}
void AppWnd::OnReceiveFiles(LArray &Files)
{
if (Files.Length() > 0)
{
if (OpenFile(Files[0], false) &&
Files.Length() > 1)
{
Doc->CompareFile(Files[1]);
}
}
}
bool AppWnd::OnRequestClose(bool OsShuttingDown)
{
if (Doc && Doc->IsDirty())
{
int r = LgiMsg(this, "Do you want to save?", AppName, OsShuttingDown ? MB_YESNO : MB_YESNOCANCEL);
if (r == IDCANCEL)
return false;
if (r == IDYES)
{
Doc->Save([](auto status)
{
LCloseApp();
});
return false;
}
}
if (!Active)
return LWindow::OnRequestClose(OsShuttingDown);
return false;
}
void AppWnd::OnDirty(bool NewValue)
{
CmdSave.Enabled(NewValue);
CmdSaveAs.Enabled(NewValue);
// CmdClose.Enabled(Doc && Doc->HasFile());
// CmdChangeSize.Enabled(Doc && Doc->HasFile());
}
bool AppWnd::OnKey(LKey &k)
{
return false;
}
void AppWnd::OnPosChange()
{
LDocApp::OnPosChange();
}
int AppWnd::OnNotify(LViewI *Ctrl, LNotification n)
{
switch (Ctrl->GetId())
{
case IDC_HEX_VIEW:
{
if (n.Type == LNotifyCursorChanged)
{
char *Data;
size_t Len;
if (Visual && Doc->GetDataAtCursor(Data, Len))
{
Visual->Visualise(Data, Len, GetCtrlValue(IDC_LITTLE) );
}
if (TextView && Doc->GetDataAtCursor(Data, Len))
{
LString Cs = Charset ? Charset->Name() : "utf-8";
if (Cs.Equals("us-ascii"))
{
LStringPipe p(1024);
for (char *s = Data; s < Data + Len && s < Data + (4 << 10) && *s; s++)
{
if (*s >= ' ' || *s == '\n' || *s == '\t')
p.Push(s, 1);
}
LString t = p.NewLStr();
TextView->Name(t);
}
else
{
LAutoString t((char*)LNewConvertCp("utf-8", Data, Cs, MIN(Len, 8<<10)));
TextView->Name(t);
}
}
auto SelLen = Doc->GetSelectedNibbles();
char s[256];
sprintf_s(s, sizeof(s), "Selection: %.1f bytes", (double)SelLen/2.0);
StatusInfo[1]->Name(SelLen ? s : NULL);
}
break;
}
}
return LDocApp::OnNotify(Ctrl, n);
}
void AppWnd::OnPulse()
{
}
LMessage::Result AppWnd::OnEvent(LMessage *Msg)
{
return LDocApp::OnEvent(Msg);
}
void AppWnd::OnPaint(LSurface *pDC)
{
pDC->Colour(L_MED);
pDC->Rectangle();
}
#define SPLIT_X 590
void AppWnd::ToggleVisualise()
{
if (GetCtrlValue(IDM_VISUALISE))
{
if (!Split)
Split = new LBox;
if (Split)
{
LString DefVisual;
LAppInst->GetOption("visual", DefVisual);
Doc->Detach();
Split->Value(SPLIT_X);
Split->Attach(this);
Split->AddView(Doc);
Split->AddView(Visual = new LVisualiseView(this, DefVisual));
Split->AttachChildren();
}
}
else
{
Doc->Detach();
DeleteObj(Split);
Doc->Attach(this);
Visual = NULL;
}
PourAll();
}
class TextBox : public LBox
{
LTableLayout *Tbl;
public:
LCombo *Charset;
LTextView3 *TextView;
TextBox()
{
SetVertical(true);
AddView(Tbl = new LTableLayout(99));
auto c = Tbl->GetCell(0, 0);
c->VerticalAlign(LCss::VerticalMiddle);
c->Add(new LTextLabel(-1, 0, 0, -1, -1, "Interpret with charset:"));
c = Tbl->GetCell(1, 0);
c->Add(Charset = new LCombo(98, 0, 0, -1, -1));
for (LCharset *cs = LGetCsList(); cs->Charset; cs++)
Charset->Insert(cs->Charset);
Tbl->GetCss(true)->Height("30px");
AddView(TextView = new LTextView3(100, 0, 0, 100, 100, 0));
}
int OnNotify(LViewI *c, LNotification n)
{
return 0;
}
};
void AppWnd::ToggleTextView()
{
if (GetCtrlValue(IDM_TEXTVIEW))
{
SetCtrlValue(IDM_VISUALISE, false);
if (!Split)
Split = new LBox;
if (Split)
{
Doc->Detach();
Split->Value(SPLIT_X);
Split->Attach(this);
Split->AddView(Doc);
TextBox *t = new TextBox;
Split->AddView(t);
TextView = t->TextView;
Charset = t->Charset;
Split->AttachChildren();
}
}
else
{
Doc->Detach();
DeleteObj(Split);
Doc->Attach(this);
TextView = NULL;
Charset = NULL;
}
PourAll();
}
int Cmp(const char **a, const char **b)
{
return stricmp(*a, *b);
}
int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd)
{
switch (Cmd)
{
case IDM_COPY_HEX:
{
if (!Doc)
break;
Doc->Copy(FmtHex);
break;
}
case IDM_COPY_TEXT:
{
if (!Doc)
break;
Doc->Copy(FmtText);
break;
}
case IDM_COPY_CODE:
{
if (!Doc)
break;
Doc->Copy(FmtCode);
break;
}
case IDM_PASTE:
{
if (!Doc)
break;
Doc->Paste(FmtHex);
break;
}
case IDM_PASTE_BINARY:
{
if (!Doc)
break;
Doc->Paste(FmtText);
break;
}
case IDM_COMBINE_FILES:
{
auto s = new LFileSelect;
s->Parent(this);
s->MultiSelect(true);
s->Open([this](auto s, auto status)
{
if (status)
{
int64 Size = 0;
size_t i;
for (i=0; iLength(); i++)
Size += LFileSize((*s)[i]);
auto o = new LFileSelect;
o->Save([this,Size,s](auto o, auto ok)
{
if (ok)
{
LFile Out;
if (!Out.Open(o->Name(), O_WRITE))
{
LgiTrace("%s:%i - Can't open %s\n", _FL, o->Name());
}
else
{
LProgressDlg Dlg(this);
Dlg.SetRange(Size);
Dlg.SetType("MB");
Dlg.SetScale(1.0/1024.0/1024.0);
LArray Buf;
Buf.Length(1 << 20);
Out.SetSize(0);
LArray Files;
for (auto i=0; iLength(); i++)
Files[i] = (*s)[i];
Files.Sort(Cmp);
for (auto i=0; i 0)
{
auto w = Out.Write(&Buf[0], r);
if (w != r)
{
LgiTrace("%s:%i - Write error...!\n", _FL);
break;
}
p += w;
Dlg.Value(Dlg.Value() + w);
// LYield();
}
else break;
}
}
else LgiTrace("%s:%i - Can't open %s\n", _FL, Files[i]);
}
}
}
delete o;
});
}
delete s;
});
break;
}
case IDM_VISUALISE:
{
if (GetCtrlValue(IDM_TEXTVIEW))
{
SetCtrlValue(IDM_TEXTVIEW, false);
ToggleTextView();
}
ToggleVisualise();
OnNotify(Doc, LNotifyCursorChanged);
break;
}
case IDM_TEXTVIEW:
{
if (GetCtrlValue(IDM_VISUALISE))
{
SetCtrlValue(IDM_VISUALISE, false);
ToggleVisualise();
}
ToggleTextView();
OnNotify(Doc, LNotifyCursorChanged);
break;
}
case IDM_SAVE:
{
if (Doc)
Doc->Save(NULL);
break;
}
case IDM_EXIT:
{
if (Doc)
Doc->Empty();
LCloseApp();
break;
}
case IDM_NEW_BUFFER:
{
if (!Doc)
break;
Doc->CreateFile(256);
break;
}
case IDM_CLOSE:
{
if (Doc)
Doc->CloseFile();
else
LCloseApp();
break;
}
case IDM_SAVE_SELECTION:
{
if (!Doc)
break;
auto s = new LFileSelect;
s->Parent(this);
s->Save([this](auto s, auto ok)
{
if (ok)
Doc->SaveSelection(NULL, s->Name());
delete s;
});
break;
}
case IDM_FILL_RND:
{
if (!Doc)
break;
RandomData Rnd;
Doc->SelectionFillRandom(&Rnd);
break;
}
case IDM_SEARCH:
{
if (SearchDlg::Inst)
delete SearchDlg::Inst;
new SearchDlg(this);
break;
}
case IDM_NEXT:
{
if (Doc && SearchDlg::Inst)
Doc->DoSearch(SearchDlg::Inst);
break;
}
case IDM_FILE_COMPARE:
{
if (!Doc || !Doc->HasFile())
break;
auto s = new LFileSelect;
s->Parent(this);
s->Open([this](auto s, auto ok)
{
if (ok)
Doc->CompareFile(s->Name());
delete s;
});
break;
}
case IDM_CHANGE_SIZE:
{
if (!Doc)
break;
LHexBuffer *Cur = Doc->GetCursorBuffer();
if (!Cur)
break;
auto Cursor = Bar->GetOffset();
auto Dlg = new ChangeSizeDlg(this, Cursor);
Dlg->DoModal([this,Dlg](auto dlg, auto code)
{
if (code)
Doc->SetFileSize(Dlg->Size);
delete dlg;
});
break;
}
case IDM_SELECT_ALL:
{
if (Doc)
Doc->SelectAll();
break;
}
case IDM_HELP:
{
Help("index.html");
break;
}
case IDM_ABOUT:
{
LAbout Dlg( this,
AppName,
APP_VER,
"\nSimple Hex Viewer",
"_about.gif",
"http://www.memecode.com/ihex.php",
"fret@memecode.com");
break;
}
}
return LDocApp::OnCommand(Cmd, Event, Wnd);
}
void AppWnd::Help(const char *File)
{
if (!File) return;
char e[MAX_PATH_LEN];
sprintf_s(e, sizeof(e), "%s",
#ifdef MAC
LGetExeFile()
#else
LGetExePath()
#endif
.Get());
#ifdef WIN32
LString Leaf = LGetLeaf(e);
if (Leaf.Find("Release") >= 0 || Leaf.Find("Debug") >= 0)
LTrimDir(e);
#elif defined(MAC)
LMakePath(e, sizeof(e), e, "Contents/Resources");
#endif
LMakePath(e, sizeof(e), e, "Help");
LMakePath(e, sizeof(e), e, File);
if (LFileExists(e))
{
LExecute(e);
}
else
{
LgiMsg(this, "The help file '%s' doesn't exist.", AppName, MB_OK, e);
}
}
void AppWnd::SetStatus(int Pos, char *Text)
{
if (Pos >= 0 && Pos < 3 && StatusInfo[Pos] && Text)
{
StatusInfo[Pos]->Name(Text);
}
}
LRect GetClient(LView *w)
{
#ifdef WIN32
RECT r = {0, 0, 0, 0};
if (w)
{
GetClientRect(w->Handle(), &r);
}
return LRect(r);
#else
return LRect(0, 0, (w)?w->X()-1:0, (w)?w->Y()-1:0);
#endif
}
bool AppWnd::OpenFile(const char *FileName, bool ReadOnly)
{
bool Status = false;
if (Doc)
{
- Status = Doc->OpenFile(FileName, ReadOnly);
- OnDocument(Status);
- OnDirty(GetDirty());
- SetCurFile(FileName);
+ Doc->OpenFile(
+ FileName,
+ ReadOnly,
+ [this, FileName=LString(FileName)](auto Status)
+ {
+ OnDocument(Status);
+ OnDirty(GetDirty());
+ if (Status)
+ SetCurFile(FileName);
+ });
}
return Status;
}
void AppWnd::SaveFile(const char *FileName, std::function Callback)
{
bool Status = false;
if (Doc)
Status = Doc->SaveFile(NULL, FileName);
if (Callback)
Callback(FileName, Status);
}
bool AppWnd::Empty()
{
return (Doc) ? Doc->Empty() : true;
}
void AppWnd::OnDocument(bool Valid)
{
- // LgiTrace("%s:%i - OnDocument(%i)\n", _FL, Valid);
+ LgiTrace("%s:%i - OnDocument(%i)\n", _FL, Valid);
+
CmdFind.Enabled(Valid);
CmdNext.Enabled(Valid);
CmdVisualise.Enabled(Valid);
CmdText.Enabled(Valid);
bool Dirt = GetDirty();
CmdSave.Enabled(Valid && Dirt);
CmdSaveAs.Enabled(Valid);
CmdClose.Enabled(Valid);
CmdChangeSize.Enabled(Valid);
}
//////////////////////////////////////////////////////////////////
int LgiMain(OsAppArguments &AppArgs)
{
LResources::SetLoadStyles(true);
LApp a(AppArgs, "i.Hex");
if (a.IsOk())
{
#if 0
auto s = LFile::Path::PrintAll();
printf("%s", s.Get());
#endif
a.AppWnd = new AppWnd;
a.Run();
}
return 0;
}
diff --git a/src/iHexView.h b/src/iHexView.h
--- a/src/iHexView.h
+++ b/src/iHexView.h
@@ -1,292 +1,292 @@
#ifndef _HEX_VIEW_H_
#define _HEX_VIEW_H_
#include "Diff.h"
///////////////////////////////////////////////////////////////////////////////////////////////
enum PaneType
{
HexPane,
AsciiPane
};
enum FormatType
{
FmtText,
FmtHex,
FmtCode
};
class IHexBar;
class LHexView;
class LHexBuffer
{
LHexView *View;
bool IsAsking = false;
public:
// File
LFile *File;
int64 Size;
int Used;
bool IsReadOnly; // Data is read only
bool IsDirty;
// Buffer
uchar *Buf; // Buffer for data from the file
size_t BufLen; // Length of the data buffer
size_t BufUsed; // Length of the buffer used
int64 BufPos; // Where the start of the buffer came from in the file
// Position
LRect Pos;
// Layout
LString::Array Content;
LHexBuffer(LHexView *view)
{
View = view;
File = NULL;
Buf = NULL;
BufLen = 0;
BufUsed = 0;
BufPos = 0;
Size = 0;
Used = 0;
IsDirty = false;
IsReadOnly = false;
Content.SetFixedLength(false);
}
~LHexBuffer()
{
Empty();
}
int64 SetSize(size_t sz)
{
if (File)
{
Size = File->SetSize(sz);
}
else // Memory buffer... resize the memory
{
size_t Common = MIN((size_t)Size, sz);
uchar *NewBuf = new uchar[sz];
if (!NewBuf)
{
return -1;
}
if (Common)
memcpy(NewBuf, Buf, (size_t)Common);
if (Common < sz)
memset(NewBuf+Common, 0, (size_t)(sz - Common));
delete [] Buf;
Buf = NewBuf;
BufUsed = sz;
BufLen = sz;
Size = sz;
}
return Size;
}
bool Open(const char *FileName, bool ReadOnly)
{
Empty();
File = new LFile;
if (!File)
return false;
if (!File->Open(FileName, ReadOnly ? O_READ : O_READWRITE))
{
if (!ReadOnly && File->Open(FileName, O_READ))
IsReadOnly = true;
}
else
{
IsReadOnly = false;
}
if (!File->IsOpen())
return false;
Size = File->GetSize();
return true;
}
void Empty()
{
DeleteObj(File);
DeleteArray(Buf);
BufLen = 0;
BufUsed = 0;
BufPos = 0;
Used = 0;
Size = 0;
}
bool HasData()
{
return File != 0 ||
(Buf != 0 && BufUsed > 0);
}
bool Save();
void SetDirty(bool Dirty = true);
bool GetData(int64 Start, size_t Len);
bool GetLocationOfByte(LArray &Loc, int64 Offset, const char16 *LineBuf);
void OnPaint(LSurface *pDC, int64 Start, int64 Len, LHexBuffer *Compare);
};
struct GHexCursor
{
LHexBuffer *Buf = NULL;
int BufIndex; // Index of 'Buf'
int64 Offset; // Offset into the file that the cursor is over
int Nibble; // 0 or 1, defining the nibble pointed to
// 0: 0xc0, 1: 0x0c etc
PaneType Pane; // 0 = hex, 1 = ascii
uint64 FlashTs;
bool Flash;
LArray Pos;
GHexCursor()
{
Empty();
}
GHexCursor &operator =(const GHexCursor &c)
{
Buf = c.Buf;
Offset = c.Offset;
Nibble = c.Nibble;
return *this;
}
void Empty()
{
Buf = NULL;
FlashTs = 0;
Offset = -1;
Nibble = 0;
Pane = HexPane;
Flash = true;
Pos.Length(0);
}
};
class LHexView : public LLayout
{
friend class LHexBuffer;
AppWnd *App;
IHexBar *Bar;
LFont *Font;
LArray DbgRect;
// General display parameters
bool IsHex; // Display offsets in hex
LPoint CharSize; // Size of character in pixels
int BytesPerLine; // Number of bytes to display on each line
int IntWidth; // Number of bytes to display in one contiguous number
// Data buffers
LArray Buf;
// Comparison file
diff_info DiffInfo;
struct Layout
{
int Len[2];
int64 Offset[2];
uint8_t *Data[2];
LRect Pos[2];
bool Same;
Layout()
{
Same = false;
Len[0] = Len[1] = 0;
Offset[0] = Offset[1] = 0;
Data[0] = Data[1] = NULL;
}
};
LArray CmpLayout;
// void PaintLayout(LSurface *pDC, Layout &l, LRect &client);
// Cursors
GHexCursor Cursor, Selection;
void SwapBytes(void *p, int Len);
void InvalidateByte(int64 Idx);
public:
LHexView(AppWnd *app, IHexBar *bar);
~LHexView();
AppWnd *GetApp() { return App; }
bool CreateFile(int64 Len);
- bool OpenFile(const char *FileName, bool ReadOnly);
+ void OpenFile(const char *FileName, bool ReadOnly, std::function Callback);
bool SaveFile(LHexBuffer *b, const char *FileName);
bool CloseFile(ssize_t Index = -1);
void Save(std::function callback);
bool IsDirty();
bool HasFile();
bool Empty();
void SaveSelection(LHexBuffer *b, const char *File);
void SelectionFillRandom(LStream *Rnd);
void SelectAll();
void CompareFile(const char *File);
void Copy(FormatType Fmt);
void Paste(FormatType Fmt);
bool HasSelection();
int64 GetSelectedNibbles();
void UpdateScrollBar();
LHexBuffer *GetCursorBuffer();
void SetCursor(LHexBuffer *b, int64 cursor, int nibble = 0, bool select = false);
void SetIsHex(bool i);
int64 GetFileSize();
void SetFileSize(int64 Size);
void DoInfo();
int64 Search(SearchDlg *For, uchar *Bytes, size_t Len);
void DoSearch(SearchDlg *For);
bool GetCursorFromLoc(int x, int y, GHexCursor &c);
bool GetDataAtCursor(char *&Data, size_t &Len);
void SetBit(uint8_t Bit, bool On);
void SetByte(uint8_t Byte);
void SetShort(uint16 Byte);
void SetInt(uint32_t Byte);
void InvalidateCursor();
void InvalidateLines(LArray &a, LArray &b);
bool Pour(LRegion &r);
int OnNotify(LViewI *c, LNotification n);
void OnPosChange();
void OnPaint(LSurface *pDC);
void OnMouseClick(LMouse &m);
void OnMouseMove(LMouse &m);
void OnFocus(bool f);
bool OnKey(LKey &k);
bool OnMouseWheel(double Lines);
void OnPulse();
void OnCreate();
};
#endif