diff --git a/src/iHex.cpp b/src/iHex.cpp --- a/src/iHex.cpp +++ b/src/iHex.cpp @@ -1,3468 +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); + sprintf(s, "%2.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->Name(Valid ? sDec.Get() : NULL); + Hex->Name(Valid ? sHex.Get() : 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->Name(Valid ? sDec.Get() : NULL); + Hex->Name(Valid ? sHex.Get() : 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; } void LHexView::OpenFile(const char *FileName, bool ReadOnly, std::function Callback) { 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(); } 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); }); } 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); + sprintf(s, "%i:%2.2i:%2.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) { 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); 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/iHex.h b/src/iHex.h --- a/src/iHex.h +++ b/src/iHex.h @@ -1,164 +1,164 @@ /*hdr ** FILE: iHex.h ** AUTHOR: Matthew Allen ** DESCRIPTION: Main header ** ** Copyright (C) 2005, Matthew Allen ** fret@memecode.com */ #ifndef __IDISK_H #define __IDISK_H #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/About.h" #include "lgi/common/DocApp.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/Splitter.h" #include "lgi/common/TextView3.h" ///////////////////////////////////////////////////////////////////////////// #define APP_VER "1.2" ///////////////////////////////////////////////////////////////////////////// enum Commands { IDM_START = 100, IDM_REWIND, IDM_PLAY, IDM_PAUSE, IDM_STOP, IDM_FORWARD, IDM_END, IDM_VISUALISE, IDM_NEW, IDM_DELETE, IDM_COMPILE, IDM_TEXTVIEW, IDM_LOCK, }; enum Controls { IDC_HEX_VIEW = 1000, IDC_LIST, }; #define MAX_SIZES 8 #define C_WHITE Rgb24(255, 255, 255) #ifdef WIN32 #define C_HIGHLIGHT GetSysColor(COLOR_HIGHLIGHT) #define C_TEXT GetSysColor(COLOR_BTNTEXT) #define C_WND_TEXT GetSysColor(COLOR_WINDOWTEXT) #else #define C_HIGHLIGHT Rgb24(0xC0, 0xC0, 0xC0) #define C_TEXT Rgb24(0, 0, 0) #define C_WND_TEXT Rgb24(0, 0, 0) #endif ///////////////////////////////////////////////////////////////////////////// extern const char *AppName; // extern char16 *LexCpp(char16 *&s, bool ReturnString = true); ///////////////////////////////////////////////////////////////////////////// class AppWnd : public LDocApp, public LScriptContext { // state bool Active = false; // views LToolBar *Tools = NULL; class LHexView *Doc = NULL; class IHexBar *Bar = NULL; class LVisualiseView *Visual = NULL; LTextView3 *TextView = NULL; LViewI *Charset = NULL; LCommand CmdSave; LCommand CmdSaveAs; LCommand CmdClose; LCommand CmdChangeSize; LCommand CmdFind; LCommand CmdNext; LCommand CmdVisualise; LCommand CmdText; LBox *Split = NULL; LStatusBar *Status = NULL; LStatusPane *StatusInfo[3]; void ToggleVisualise(); void ToggleTextView(); LHostFunc *GetCommands() { return 0; } void SetEngine(LScriptEngine *Eng) {} - LString GetIncludeFile(const char *FileName) override { return NULL; } + LString GetIncludeFile(const char *FileName) override { return LString(); } public: AppWnd(); ~AppWnd(); void SetStatus(int Pos, char *Text); bool OpenFile(const char *FileName, bool ReadOnly); void SaveFile(const char *FileName, std::function Callback); void OnDocument(bool Valid); LHexView *GetDoc() { return Doc; } bool Empty(); bool OnKey(LKey &k); void OnPosChange(); LMessage::Result OnEvent(LMessage *Msg); void OnPaint(LSurface *pDC); int OnCommand(int Cmd, int Event, OsView Wnd); void OnPulse(); int OnNotify(LViewI *Ctrl, LNotification n); bool OnRequestClose(bool OsShuttingDown); void OnDirty(bool NewValue); void Help(const char *File); void OnReceiveFiles(LArray &Files); }; class SearchDlg : public LDialog { AppWnd *App; public: static SearchDlg *Inst; bool ForHex; bool MatchWord; bool MatchCase; bool SearchUp; uchar *Bin; int64 Length; SearchDlg(AppWnd *app); ~SearchDlg(); int OnNotify(LViewI *c, LNotification n); void OnCreate(); }; #include "lgi/common/TextView3.h" class LVisualiseView : public LSplitter { AppWnd *App; class LMapWnd *Map; LTextView3 *Txt; char Base[MAX_PATH_LEN]; public: LVisualiseView(AppWnd *app, char *DefVisual = NULL); int OnNotify(LViewI *c, LNotification n); void Visualise(char *Data, size_t Len, bool Little); }; #endif