Debugger: LUA scripting

This commit is contained in:
Souryo 2017-08-30 18:31:27 -04:00
parent 8e2f39e3bd
commit 5957bc2d3e
181 changed files with 48489 additions and 103 deletions

View file

@ -254,4 +254,16 @@ bool APU::IsApuEnabled()
//load over the entire PPU frame, like what was done before. //load over the entire PPU frame, like what was done before.
//This is most likely due to the timing of the Frame Counter & DMC IRQs. //This is most likely due to the timing of the Frame Counter & DMC IRQs.
return _apuEnabled; return _apuEnabled;
}
ApuState APU::GetState()
{
ApuState state;
state.Dmc = _deltaModulationChannel->GetState();
state.FrameCounter = _frameCounter->GetState();
state.Noise = _noiseChannel->GetState();
state.Square1 = _squareChannel[0]->GetState();
state.Square2 = _squareChannel[1]->GetState();
state.Triangle = _triangleChannel->GetState();
return state;
} }

View file

@ -62,6 +62,8 @@ class APU : public Snapshotable, public IMemoryHandler
void WriteRAM(uint16_t addr, uint8_t value) override; void WriteRAM(uint16_t addr, uint8_t value) override;
void GetMemoryRanges(MemoryRanges &ranges) override; void GetMemoryRanges(MemoryRanges &ranges) override;
ApuState GetState();
void Exec(); void Exec();
__forceinline static void ExecStatic() __forceinline static void ExecStatic()

View file

@ -81,4 +81,16 @@ public:
_divider = _volume; _divider = _volume;
} }
} }
ApuEnvelopeState GetState()
{
ApuEnvelopeState state;
state.ConstantVolume = _constantVolume;
state.Counter = _counter;
state.Divider = _divider;
state.Loop = _lengthCounterHalt;
state.StartFlag = _start;
state.Volume = _volume;
return state;
}
}; };

View file

@ -214,4 +214,13 @@ public:
CPU::ClearIRQSource(IRQSource::FrameCounter); CPU::ClearIRQSource(IRQSource::FrameCounter);
} }
} }
ApuFrameCounterState GetState()
{
ApuFrameCounterState state;
state.IrqEnabled = !_inhibitIRQ;
state.SequencePosition = _currentStep;
state.FiveStepMode = _stepMode == 1;
return state;
}
}; };

View file

@ -6,11 +6,11 @@ class ApuLengthCounter : public BaseApuChannel
{ {
private: private:
uint8_t _lcLookupTable[32] = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 }; uint8_t _lcLookupTable[32] = { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 };
bool _enabled = false;
bool _newHaltValue; bool _newHaltValue;
static bool _needToRun; static bool _needToRun;
protected: protected:
bool _enabled = false;
bool _lengthCounterHalt; bool _lengthCounterHalt;
uint8_t _lengthCounter; uint8_t _lengthCounter;
uint8_t _lengthCounterReloadValue; uint8_t _lengthCounterReloadValue;
@ -112,4 +112,13 @@ public:
} }
_enabled = enabled; _enabled = enabled;
} }
ApuLengthCounterState GetState()
{
ApuLengthCounterState state;
state.Counter = _lengthCounter;
state.Halt = _lengthCounterHalt;
state.ReloadValue = _lengthCounterReloadValue;
return state;
}
}; };

View file

@ -9,12 +9,12 @@ class BaseApuChannel : public IMemoryHandler, public Snapshotable
{ {
private: private:
SoundMixer *_mixer; SoundMixer *_mixer;
int8_t _lastOutput;
uint32_t _previousCycle; uint32_t _previousCycle;
AudioChannel _channel; AudioChannel _channel;
NesModel _nesModel; NesModel _nesModel;
protected: protected:
int8_t _lastOutput;
uint16_t _timer = 0; uint16_t _timer = 0;
uint16_t _period = 0; uint16_t _period = 0;

View file

@ -737,6 +737,16 @@ uint8_t BaseMapper::ReadRAM(uint16_t addr)
return MemoryManager::GetOpenBus(); return MemoryManager::GetOpenBus();
} }
uint8_t BaseMapper::DebugReadRAM(uint16_t addr)
{
if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Read) {
return _prgPages[addr >> 8][(uint8_t)addr];
} else {
//assert(false);
}
return MemoryManager::GetOpenBus();
}
void BaseMapper::WriteRAM(uint16_t addr, uint8_t value) void BaseMapper::WriteRAM(uint16_t addr, uint8_t value)
{ {
if(_isWriteRegisterAddr[addr]) { if(_isWriteRegisterAddr[addr]) {
@ -749,6 +759,17 @@ void BaseMapper::WriteRAM(uint16_t addr, uint8_t value)
} }
} }
void BaseMapper::DebugWriteRAM(uint16_t addr, uint8_t value)
{
if(_isWriteRegisterAddr[addr]) {
if(_hasBusConflicts) {
value &= _prgPages[addr >> 8][(uint8_t)addr];
}
} else {
WritePrgRam(addr, value);
}
}
void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value) void BaseMapper::WritePrgRam(uint16_t addr, uint8_t value)
{ {
if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Write) { if(_prgPageAccessType[addr >> 8] & MemoryAccessType::Write) {

View file

@ -168,7 +168,9 @@ public:
RomFormat GetRomFormat(); RomFormat GetRomFormat();
__forceinline uint8_t ReadRAM(uint16_t addr) override; __forceinline uint8_t ReadRAM(uint16_t addr) override;
uint8_t DebugReadRAM(uint16_t addr);
virtual void WriteRAM(uint16_t addr, uint8_t value) override; virtual void WriteRAM(uint16_t addr, uint8_t value) override;
void DebugWriteRAM(uint16_t addr, uint8_t value);
void WritePrgRam(uint16_t addr, uint8_t value); void WritePrgRam(uint16_t addr, uint8_t value);
__forceinline uint8_t InternalReadVRAM(uint16_t addr); __forceinline uint8_t InternalReadVRAM(uint16_t addr);

View file

@ -13,6 +13,11 @@ CheatManager::CheatManager()
} }
} }
CheatManager * CheatManager::GetInstance()
{
return Instance;
}
uint32_t CheatManager::DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount) uint32_t CheatManager::DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount)
{ {
uint32_t result = 0; uint32_t result = 0;

View file

@ -41,14 +41,16 @@ private:
CodeInfo GetPARCodeInfo(uint32_t parCode); CodeInfo GetPARCodeInfo(uint32_t parCode);
void AddCode(CodeInfo &code); void AddCode(CodeInfo &code);
public:
CheatManager();
static CheatManager* GetInstance();
void AddGameGenieCode(string code); void AddGameGenieCode(string code);
void AddProActionRockyCode(uint32_t code); void AddProActionRockyCode(uint32_t code);
void AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue = -1, bool isRelativeAddress = true); void AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue = -1, bool isRelativeAddress = true);
void ClearCodes(); void ClearCodes();
public:
CheatManager();
static vector<CodeInfo> GetCheats(); static vector<CodeInfo> GetCheats();
static void SetCheats(vector<CodeInfo> &cheats); static void SetCheats(vector<CodeInfo> &cheats);
static void SetCheats(CheatInfo cheats[], uint32_t length); static void SetCheats(CheatInfo cheats[], uint32_t length);

View file

@ -287,17 +287,7 @@ void Console::ResetComponents(bool softReset)
SoundMixer::StopAudio(true); SoundMixer::StopAudio(true);
if(softReset) { MessageManager::SendNotification(softReset ? ConsoleNotificationType::GameReset : ConsoleNotificationType::GameLoaded);
if(_debugger) {
auto lock = _debuggerLock.AcquireSafe();
StopDebugger();
GetDebugger();
}
MessageManager::SendNotification(ConsoleNotificationType::GameReset);
} else {
MessageManager::SendNotification(ConsoleNotificationType::GameLoaded);
}
} }
void Console::Stop() void Console::Stop()
@ -380,6 +370,10 @@ void Console::Run()
_lagCounter++; _lagCounter++;
} }
if(_debugger) {
_debugger->ProcessEvent(EventType::StartFrame);
}
_rewindManager->ProcessEndOfFrame(); _rewindManager->ProcessEndOfFrame();
EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance()); EmulationSettings::DisableOverclocking(_disableOcNextFrame || NsfMapper::GetInstance());
_disableOcNextFrame = false; _disableOcNextFrame = false;
@ -485,7 +479,7 @@ void Console::Run()
bool Console::IsRunning() bool Console::IsRunning()
{ {
return !Instance->_stopLock.IsFree(); return !Instance->_stopLock.IsFree() && !Instance->_runLock.IsFree();
} }
void Console::UpdateNesModel(bool sendNotification) void Console::UpdateNesModel(bool sendNotification)
@ -597,7 +591,7 @@ std::shared_ptr<Debugger> Console::GetDebugger(bool autoStart)
{ {
auto lock = _debuggerLock.AcquireSafe(); auto lock = _debuggerLock.AcquireSafe();
if(!_debugger && autoStart) { if(!_debugger && autoStart) {
_debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _memoryManager, _mapper)); _debugger.reset(new Debugger(Console::Instance, _cpu, _ppu, _apu, _memoryManager, _mapper));
} }
return _debugger; return _debugger;
} }

View file

@ -32,7 +32,7 @@ class Console
shared_ptr<RewindManager> _rewindManager; shared_ptr<RewindManager> _rewindManager;
shared_ptr<CPU> _cpu; shared_ptr<CPU> _cpu;
shared_ptr<PPU> _ppu; shared_ptr<PPU> _ppu;
unique_ptr<APU> _apu; shared_ptr<APU> _apu;
shared_ptr<Debugger> _debugger; shared_ptr<Debugger> _debugger;
SimpleLock _debuggerLock; SimpleLock _debuggerLock;
shared_ptr<BaseMapper> _mapper; shared_ptr<BaseMapper> _mapper;

View file

@ -412,12 +412,20 @@
<ClInclude Include="Assembler.h" /> <ClInclude Include="Assembler.h" />
<ClInclude Include="AutomaticRomTest.h" /> <ClInclude Include="AutomaticRomTest.h" />
<ClInclude Include="BaseRenderer.h" /> <ClInclude Include="BaseRenderer.h" />
<ClInclude Include="DebugHud.h" />
<ClInclude Include="DrawCommand.h" />
<ClInclude Include="DrawLineCommand.h" />
<ClInclude Include="DrawPixelCommand.h" />
<ClInclude Include="DrawStringCommand.h" />
<ClInclude Include="FceuxMovie.h" /> <ClInclude Include="FceuxMovie.h" />
<ClInclude Include="HdAudioDevice.h" /> <ClInclude Include="HdAudioDevice.h" />
<ClInclude Include="HdBuilderPpu.h" /> <ClInclude Include="HdBuilderPpu.h" />
<ClInclude Include="HdData.h" /> <ClInclude Include="HdData.h" />
<ClInclude Include="HdPackBuilder.h" /> <ClInclude Include="HdPackBuilder.h" />
<ClInclude Include="HdPackLoader.h" /> <ClInclude Include="HdPackLoader.h" />
<ClInclude Include="LuaApi.h" />
<ClInclude Include="LuaCallHelper.h" />
<ClInclude Include="LuaScriptingContext.h" />
<ClInclude Include="Mapper174.h" /> <ClInclude Include="Mapper174.h" />
<ClInclude Include="OggMixer.h" /> <ClInclude Include="OggMixer.h" />
<ClInclude Include="OggReader.h" /> <ClInclude Include="OggReader.h" />
@ -461,6 +469,8 @@
<ClInclude Include="MesenMovie.h" /> <ClInclude Include="MesenMovie.h" />
<ClInclude Include="RewindData.h" /> <ClInclude Include="RewindData.h" />
<ClInclude Include="RewindManager.h" /> <ClInclude Include="RewindManager.h" />
<ClInclude Include="ScriptHost.h" />
<ClInclude Include="ScriptingContext.h" />
<ClInclude Include="SealieComputing.h" /> <ClInclude Include="SealieComputing.h" />
<ClInclude Include="UnlD1038.h" /> <ClInclude Include="UnlD1038.h" />
<ClInclude Include="DaouInfosys.h" /> <ClInclude Include="DaouInfosys.h" />
@ -780,11 +790,16 @@
<ClCompile Include="Assembler.cpp" /> <ClCompile Include="Assembler.cpp" />
<ClCompile Include="AutomaticRomTest.cpp" /> <ClCompile Include="AutomaticRomTest.cpp" />
<ClCompile Include="BaseRenderer.cpp" /> <ClCompile Include="BaseRenderer.cpp" />
<ClCompile Include="DebugHud.cpp" />
<ClCompile Include="DrawRectangleCommand.h" />
<ClCompile Include="FceuxMovie.cpp" /> <ClCompile Include="FceuxMovie.cpp" />
<ClCompile Include="HdAudioDevice.cpp" /> <ClCompile Include="HdAudioDevice.cpp" />
<ClCompile Include="HdNesPack.cpp" /> <ClCompile Include="HdNesPack.cpp" />
<ClCompile Include="HdPackBuilder.cpp" /> <ClCompile Include="HdPackBuilder.cpp" />
<ClCompile Include="HdPackLoader.cpp" /> <ClCompile Include="HdPackLoader.cpp" />
<ClCompile Include="LuaApi.cpp" />
<ClCompile Include="LuaCallHelper.cpp" />
<ClCompile Include="LuaScriptingContext.cpp" />
<ClCompile Include="OggMixer.cpp" /> <ClCompile Include="OggMixer.cpp" />
<ClCompile Include="OggReader.cpp" /> <ClCompile Include="OggReader.cpp" />
<ClCompile Include="RecordedRomTest.cpp" /> <ClCompile Include="RecordedRomTest.cpp" />
@ -829,6 +844,8 @@
<ClCompile Include="RewindData.cpp" /> <ClCompile Include="RewindData.cpp" />
<ClCompile Include="RewindManager.cpp" /> <ClCompile Include="RewindManager.cpp" />
<ClCompile Include="RomLoader.cpp" /> <ClCompile Include="RomLoader.cpp" />
<ClCompile Include="ScriptHost.cpp" />
<ClCompile Include="ScriptingContext.cpp" />
<ClCompile Include="ShortcutKeyHandler.cpp" /> <ClCompile Include="ShortcutKeyHandler.cpp" />
<ClCompile Include="Snapshotable.cpp" /> <ClCompile Include="Snapshotable.cpp" />
<ClCompile Include="SoundMixer.cpp" /> <ClCompile Include="SoundMixer.cpp" />

View file

@ -92,6 +92,15 @@
<Filter Include="HdPacks"> <Filter Include="HdPacks">
<UniqueIdentifier>{a6994cb5-f9d2-416c-84ab-c1abe4975eb1}</UniqueIdentifier> <UniqueIdentifier>{a6994cb5-f9d2-416c-84ab-c1abe4975eb1}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Debugger\Scripting">
<UniqueIdentifier>{ee232799-5562-4fea-946b-844786d6fd66}</UniqueIdentifier>
</Filter>
<Filter Include="Debugger\Scripting\DebugHud">
<UniqueIdentifier>{9e32ff0a-5fae-47bc-8007-404fd4d0204c}</UniqueIdentifier>
</Filter>
<Filter Include="Debugger\Scripting\Lua">
<UniqueIdentifier>{e1e8a5d2-aa9a-40e1-94eb-00adec1cdef3}</UniqueIdentifier>
</Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="IAudioDevice.h"> <ClInclude Include="IAudioDevice.h">
@ -1201,6 +1210,36 @@
<ClInclude Include="OggMixer.h"> <ClInclude Include="OggMixer.h">
<Filter>HdPacks</Filter> <Filter>HdPacks</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="ScriptHost.h">
<Filter>Debugger\Scripting</Filter>
</ClInclude>
<ClInclude Include="ScriptingContext.h">
<Filter>Debugger\Scripting</Filter>
</ClInclude>
<ClInclude Include="DebugHud.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClInclude>
<ClInclude Include="DrawCommand.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClInclude>
<ClInclude Include="DrawStringCommand.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClInclude>
<ClInclude Include="DrawLineCommand.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClInclude>
<ClInclude Include="DrawPixelCommand.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClInclude>
<ClInclude Include="LuaApi.h">
<Filter>Debugger\Scripting\Lua</Filter>
</ClInclude>
<ClInclude Include="LuaCallHelper.h">
<Filter>Debugger\Scripting\Lua</Filter>
</ClInclude>
<ClInclude Include="LuaScriptingContext.h">
<Filter>Debugger\Scripting\Lua</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="stdafx.cpp"> <ClCompile Include="stdafx.cpp">
@ -1443,5 +1482,26 @@
<ClCompile Include="HdAudioDevice.cpp"> <ClCompile Include="HdAudioDevice.cpp">
<Filter>Misc</Filter> <Filter>Misc</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="DebugHud.cpp">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClCompile>
<ClCompile Include="DrawRectangleCommand.h">
<Filter>Debugger\Scripting\DebugHud</Filter>
</ClCompile>
<ClCompile Include="ScriptingContext.cpp">
<Filter>Debugger\Scripting</Filter>
</ClCompile>
<ClCompile Include="LuaScriptingContext.cpp">
<Filter>Debugger\Scripting\Lua</Filter>
</ClCompile>
<ClCompile Include="LuaCallHelper.cpp">
<Filter>Debugger\Scripting\Lua</Filter>
</ClCompile>
<ClCompile Include="LuaApi.cpp">
<Filter>Debugger\Scripting\Lua</Filter>
</ClCompile>
<ClCompile Include="ScriptHost.cpp">
<Filter>Debugger\Scripting</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
</Project> </Project>

67
Core/DebugHud.cpp Normal file
View file

@ -0,0 +1,67 @@
#include "stdafx.h"
#include "DebugHud.h"
#include "DrawCommand.h"
#include "DrawLineCommand.h"
#include "DrawPixelCommand.h"
#include "DrawRectangleCommand.h"
#include "DrawStringCommand.h"
DebugHud* DebugHud::_instance = nullptr;
DebugHud::DebugHud()
{
_instance = this;
}
DebugHud::~DebugHud()
{
_commandLock.Acquire();
if(_instance == this) {
_instance = nullptr;
}
_commandLock.Release();
}
DebugHud* DebugHud::GetInstance()
{
return _instance;
}
void DebugHud::ClearScreen()
{
auto lock = _commandLock.AcquireSafe();
_commands.clear();
}
void DebugHud::Draw(uint32_t* argbBuffer, OverscanDimensions &overscan)
{
auto lock = _commandLock.AcquireSafe();
for(shared_ptr<DrawCommand> &command : _commands) {
command->Draw(argbBuffer, overscan);
}
_commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const shared_ptr<DrawCommand>& c) { return c->Expired(); }), _commands.end());
}
void DebugHud::DrawPixel(int x, int y, int color, int frameCount)
{
auto lock = _commandLock.AcquireSafe();
_commands.push_back(shared_ptr<DrawPixelCommand>(new DrawPixelCommand(x, y, color, frameCount)));
}
void DebugHud::DrawLine(int x, int y, int x2, int y2, int color, int frameCount)
{
auto lock = _commandLock.AcquireSafe();
_commands.push_back(shared_ptr<DrawLineCommand>(new DrawLineCommand(x, y, x2, y2, color, frameCount)));
}
void DebugHud::DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount)
{
auto lock = _commandLock.AcquireSafe();
_commands.push_back(shared_ptr<DrawRectangleCommand>(new DrawRectangleCommand(x, y, width, height, color, fill, frameCount)));
}
void DebugHud::DrawString(int x, int y, string text, int color, int backColor, int frameCount)
{
auto lock = _commandLock.AcquireSafe();
_commands.push_back(shared_ptr<DrawStringCommand>(new DrawStringCommand(x, y, text, color, backColor, frameCount)));
}

26
Core/DebugHud.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include "stdafx.h"
#include "../Utilities/SimpleLock.h"
#include "EmulationSettings.h"
class DrawCommand;
class DebugHud
{
private:
static DebugHud* _instance;
vector<shared_ptr<DrawCommand>> _commands;
SimpleLock _commandLock;
public:
static DebugHud* GetInstance();
DebugHud();
~DebugHud();
void Draw(uint32_t* argbBuffer, OverscanDimensions &overscan);
void ClearScreen();
void DrawPixel(int x, int y, int color, int frameCount);
void DrawLine(int x, int y, int x2, int y2, int color, int frameCount);
void DrawRectangle(int x, int y, int width, int height, int color, bool fill, int frameCount);
void DrawString(int x, int y, string text, int color, int backColor, int frameCount);
};

View file

@ -22,16 +22,19 @@
#include "MemoryManager.h" #include "MemoryManager.h"
#include "RewindManager.h" #include "RewindManager.h"
#include "DebugBreakHelper.h" #include "DebugBreakHelper.h"
#include "ScriptHost.h"
#include "DebugHud.h"
Debugger* Debugger::Instance = nullptr; Debugger* Debugger::Instance = nullptr;
const int Debugger::BreakpointTypeCount; const int Debugger::BreakpointTypeCount;
Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<PPU> ppu, shared_ptr<MemoryManager> memoryManager, shared_ptr<BaseMapper> mapper) Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<PPU> ppu, shared_ptr<APU> apu, shared_ptr<MemoryManager> memoryManager, shared_ptr<BaseMapper> mapper)
{ {
_romName = Console::GetRomName(); _romName = Console::GetRomName();
_console = console; _console = console;
_cpu = cpu; _cpu = cpu;
_ppu = ppu; _ppu = ppu;
_apu = apu;
_memoryManager = memoryManager; _memoryManager = memoryManager;
_mapper = mapper; _mapper = mapper;
@ -43,6 +46,7 @@ Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<
_memoryAccessCounter.reset(new MemoryAccessCounter(this)); _memoryAccessCounter.reset(new MemoryAccessCounter(this));
_profiler.reset(new Profiler(this)); _profiler.reset(new Profiler(this));
_traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager)); _traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager));
_debugHud.reset(new DebugHud());
_stepOut = false; _stepOut = false;
_stepCount = -1; _stepCount = -1;
@ -85,7 +89,10 @@ Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<
if(!LoadCdlFile(FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_romName, false) + ".cdl"))) { if(!LoadCdlFile(FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_romName, false) + ".cdl"))) {
_disassembler->Reset(); _disassembler->Reset();
} }
_hasScript = false;
_nextScriptId = 0;
Debugger::Instance = this; Debugger::Instance = this;
} }
@ -356,6 +363,8 @@ void Debugger::PrivateProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, b
_callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(destCpuAddr)); _callstackAbsolute.push_back(_mapper->ToAbsoluteAddress(destCpuAddr));
_profiler->StackFunction(-1, _mapper->ToAbsoluteAddress(destCpuAddr)); _profiler->StackFunction(-1, _mapper->ToAbsoluteAddress(destCpuAddr));
ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq);
} }
void Debugger::ProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi) void Debugger::ProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi)
@ -381,6 +390,9 @@ void Debugger::PrivateProcessPpuCycle()
{ {
if(PPU::GetCurrentCycle() == (uint32_t)_ppuViewerCycle && PPU::GetCurrentScanline() == _ppuViewerScanline) { if(PPU::GetCurrentCycle() == (uint32_t)_ppuViewerCycle && PPU::GetCurrentScanline() == _ppuViewerScanline) {
MessageManager::SendNotification(ConsoleNotificationType::PpuViewerDisplayFrame); MessageManager::SendNotification(ConsoleNotificationType::PpuViewerDisplayFrame);
}
if(PPU::GetCurrentCycle() == 0 && PPU::GetCurrentScanline() == 241) {
ProcessEvent(EventType::EndFrame);
} }
OperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead }; OperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead };
@ -538,6 +550,8 @@ bool Debugger::PrivateProcessRamOperation(MemoryOperationType type, uint16_t &ad
_currentReadAddr = nullptr; _currentReadAddr = nullptr;
_currentReadValue = nullptr; _currentReadValue = nullptr;
ProcessCpuOperation(addr, value, type);
if(type == MemoryOperationType::Write) { if(type == MemoryOperationType::Write) {
if(_frozenAddresses[addr]) { if(_frozenAddresses[addr]) {
return false; return false;
@ -563,6 +577,7 @@ bool Debugger::SleepUntilResume()
if(_sendNotification) { if(_sendNotification) {
SoundMixer::StopAudio(); SoundMixer::StopAudio();
MessageManager::SendNotification(ConsoleNotificationType::CodeBreak); MessageManager::SendNotification(ConsoleNotificationType::CodeBreak);
ProcessEvent(EventType::CodeBreak);
_stepOverAddr = -1; _stepOverAddr = -1;
if(CheckFlag(DebuggerFlags::PpuPartialDraw)) { if(CheckFlag(DebuggerFlags::PpuPartialDraw)) {
_ppu->DebugSendFrame(); _ppu->DebugSendFrame();
@ -594,6 +609,8 @@ void Debugger::PrivateProcessVramReadOperation(MemoryOperationType type, uint16_
SleepUntilResume(); SleepUntilResume();
} }
} }
ProcessPpuOperation(addr, value, MemoryOperationType::Read);
} }
void Debugger::PrivateProcessVramWriteOperation(uint16_t addr, uint8_t value) void Debugger::PrivateProcessVramWriteOperation(uint16_t addr, uint8_t value)
@ -606,14 +623,18 @@ void Debugger::PrivateProcessVramWriteOperation(uint16_t addr, uint8_t value)
SleepUntilResume(); SleepUntilResume();
} }
} }
ProcessPpuOperation(addr, value, MemoryOperationType::Write);
} }
void Debugger::GetState(DebugState *state, bool includeMapperInfo) void Debugger::GetState(DebugState *state, bool includeMapperInfo)
{ {
state->Model = _console->GetModel();
state->CPU = _cpu->GetState(); state->CPU = _cpu->GetState();
state->PPU = _ppu->GetState(); state->PPU = _ppu->GetState();
if(includeMapperInfo) { if(includeMapperInfo) {
state->Cartridge = _mapper->GetState(); state->Cartridge = _mapper->GetState();
state->APU = _apu->GetState();
} }
} }
@ -1013,3 +1034,71 @@ void Debugger::SetInputOverride(uint8_t port, uint32_t state)
{ {
_inputOverride[port] = state; _inputOverride[port] = state;
} }
int Debugger::LoadScript(string content, int32_t scriptId)
{
DebugBreakHelper helper(this);
if(scriptId < 0) {
shared_ptr<ScriptHost> script(new ScriptHost(_nextScriptId++));
script->LoadScript(content, this);
_scripts.push_back(script);
_hasScript = true;
return script->GetScriptId();
} else {
auto result = std::find_if(_scripts.begin(), _scripts.end(), [=](shared_ptr<ScriptHost> &script) {
return script->GetScriptId() == scriptId;
});
if(result != _scripts.end()) {
(*result)->LoadScript(content, this);
return scriptId;
}
}
return -1;
}
void Debugger::RemoveScript(int32_t scriptId)
{
DebugBreakHelper helper(this);
_scripts.erase(std::remove_if(_scripts.begin(), _scripts.end(), [=](const shared_ptr<ScriptHost>& script) { return script->GetScriptId() == scriptId; }), _scripts.end());
_hasScript = _scripts.size() > 0;
}
const char* Debugger::GetScriptLog(int32_t scriptId)
{
DebugBreakHelper helper(this);
for(shared_ptr<ScriptHost> &script : _scripts) {
if(script->GetScriptId() == scriptId) {
return script->GetLog();
}
}
return "";
}
void Debugger::ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessCpuOperation(addr, value, type);
}
}
}
void Debugger::ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessPpuOperation(addr, value, MemoryOperationType::Write);
}
}
}
void Debugger::ProcessEvent(EventType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessEvent(type);
}
}
}

View file

@ -15,6 +15,7 @@ using std::unordered_set;
#include "DebuggerTypes.h" #include "DebuggerTypes.h"
class CPU; class CPU;
class APU;
class PPU; class PPU;
class MemoryManager; class MemoryManager;
class Console; class Console;
@ -26,6 +27,8 @@ class MemoryAccessCounter;
class Profiler; class Profiler;
class CodeRunner; class CodeRunner;
class BaseMapper; class BaseMapper;
class ScriptHost;
class DebugHud;
class Debugger class Debugger
{ {
@ -43,12 +46,18 @@ private:
shared_ptr<TraceLogger> _traceLogger; shared_ptr<TraceLogger> _traceLogger;
shared_ptr<Profiler> _profiler; shared_ptr<Profiler> _profiler;
unique_ptr<CodeRunner> _codeRunner; unique_ptr<CodeRunner> _codeRunner;
unique_ptr<DebugHud> _debugHud;
shared_ptr<Console> _console; shared_ptr<Console> _console;
shared_ptr<CPU> _cpu; shared_ptr<CPU> _cpu;
shared_ptr<PPU> _ppu; shared_ptr<PPU> _ppu;
shared_ptr<APU> _apu;
shared_ptr<MemoryManager> _memoryManager; shared_ptr<MemoryManager> _memoryManager;
shared_ptr<BaseMapper> _mapper; shared_ptr<BaseMapper> _mapper;
bool _hasScript;
int _nextScriptId;
vector<shared_ptr<ScriptHost>> _scripts;
bool _bpUpdateNeeded; bool _bpUpdateNeeded;
SimpleLock _bpUpdateLock; SimpleLock _bpUpdateLock;
@ -125,7 +134,7 @@ private:
void RemoveExcessCallstackEntries(); void RemoveExcessCallstackEntries();
public: public:
Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<PPU> ppu, shared_ptr<MemoryManager> memoryManager, shared_ptr<BaseMapper> mapper); Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<PPU> ppu, shared_ptr<APU> apu, shared_ptr<MemoryManager> memoryManager, shared_ptr<BaseMapper> mapper);
~Debugger(); ~Debugger();
void SetFlags(uint32_t flags); void SetFlags(uint32_t flags);
@ -185,7 +194,7 @@ public:
static void ProcessVramReadOperation(MemoryOperationType type, uint16_t addr, uint8_t value); static void ProcessVramReadOperation(MemoryOperationType type, uint16_t addr, uint8_t value);
static void ProcessVramWriteOperation(uint16_t addr, uint8_t value); static void ProcessVramWriteOperation(uint16_t addr, uint8_t value);
static void ProcessPpuCycle(); static void ProcessPpuCycle();
static void SetLastFramePpuScroll(uint16_t x, uint16_t y); static void SetLastFramePpuScroll(uint16_t x, uint16_t y);
uint32_t GetPpuScroll(); uint32_t GetPpuScroll();
@ -210,4 +219,11 @@ public:
static bool HasInputOverride(uint8_t port); static bool HasInputOverride(uint8_t port);
static uint32_t GetInputOverride(uint8_t port); static uint32_t GetInputOverride(uint8_t port);
void SetInputOverride(uint8_t port, uint32_t state); void SetInputOverride(uint8_t port, uint32_t state);
int32_t LoadScript(string content, int32_t scriptId);
void RemoveScript(int32_t scriptId);
const char* GetScriptLog(int32_t scriptId);
void ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type);
void ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type);
void ProcessEvent(EventType type);
}; };

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "Types.h" #include "Types.h"
#include "EmulationSettings.h"
enum class DebuggerFlags enum class DebuggerFlags
{ {
@ -66,6 +67,8 @@ struct DebugState
State CPU; State CPU;
PPUDebugState PPU; PPUDebugState PPU;
CartridgeState Cartridge; CartridgeState Cartridge;
ApuState APU;
NesModel Model;
}; };
struct OperationInfo struct OperationInfo
@ -73,4 +76,15 @@ struct OperationInfo
uint16_t Address; uint16_t Address;
int16_t Value; int16_t Value;
MemoryOperationType OperationType; MemoryOperationType OperationType;
};
enum class EventType
{
Power = 0,
Reset = 1,
Nmi = 2,
Irq = 3,
StartFrame = 4,
EndFrame = 5,
CodeBreak = 6
}; };

View file

@ -5,6 +5,7 @@
#include <math.h> #include <math.h>
#include <algorithm> #include <algorithm>
#include "PPU.h" #include "PPU.h"
#include "DebugHud.h"
DefaultVideoFilter::DefaultVideoFilter() DefaultVideoFilter::DefaultVideoFilter()
{ {
@ -54,21 +55,26 @@ void DefaultVideoFilter::OnBeforeApplyFilter()
void DefaultVideoFilter::DecodePpuBuffer(uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, bool displayScanlines) void DefaultVideoFilter::DecodePpuBuffer(uint16_t *ppuOutputBuffer, uint32_t* outputBuffer, bool displayScanlines)
{ {
uint32_t* out = outputBuffer;
OverscanDimensions overscan = GetOverscan(); OverscanDimensions overscan = GetOverscan();
double scanlineIntensity = 1.0 - EmulationSettings::GetPictureSettings().ScanlineIntensity; double scanlineIntensity = 1.0 - EmulationSettings::GetPictureSettings().ScanlineIntensity;
for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) { for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) {
if(displayScanlines && (i + overscan.Top) % 2 == 0) { if(displayScanlines && (i + overscan.Top) % 2 == 0) {
for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) {
*outputBuffer = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j], scanlineIntensity); *out = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j], scanlineIntensity);
outputBuffer++; out++;
} }
} else { } else {
for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) {
*outputBuffer = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j]); *out = ProcessIntensifyBits(ppuOutputBuffer[i * 256 + j]);
outputBuffer++; out++;
} }
} }
} }
if(DebugHud::GetInstance()) {
DebugHud::GetInstance()->Draw(outputBuffer, overscan);
}
} }
void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer) void DefaultVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer)

View file

@ -210,4 +210,18 @@ bool DeltaModulationChannel::NeedToRun()
void DeltaModulationChannel::SetReadBuffer() void DeltaModulationChannel::SetReadBuffer()
{ {
Instance->FillReadBuffer(); Instance->FillReadBuffer();
}
ApuDmcState DeltaModulationChannel::GetState()
{
ApuDmcState state;
state.BytesRemaining = _bytesRemaining;
state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 32.0 / (_period + 1));
state.IrqEnabled = _irqEnabled;
state.Loop = _loopFlag;
state.OutputVolume = _lastOutput;
state.Period = _period;
state.SampleAddr = _sampleAddr;
state.SampleLength = _sampleLength;
return state;
} }

View file

@ -52,4 +52,6 @@ public:
void SetEnabled(bool enabled); void SetEnabled(bool enabled);
void StartDmcTransfer(); void StartDmcTransfer();
static void SetReadBuffer(); static void SetReadBuffer();
ApuDmcState GetState();
}; };

59
Core/DrawCommand.h Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include "stdafx.h"
#include "EmulationSettings.h"
class DrawCommand
{
private:
int _frameCount;
uint32_t* _argbBuffer;
OverscanDimensions _overscan;
protected:
virtual void InternalDraw() = 0;
__forceinline void DrawPixel(uint32_t x, uint32_t y, int color)
{
if(x < _overscan.Left || x >= _overscan.Left + _overscan.GetScreenWidth() ||
y < _overscan.Top || y >= _overscan.Top + _overscan.GetScreenHeight()) {
//In overscan (out of bounds), skip drawing
return;
}
if((color & 0xFF000000) != 0xFF000000) {
BlendColors((uint8_t*)&_argbBuffer[(y - _overscan.Top)*_overscan.GetScreenWidth() + (x - _overscan.Left)], (uint8_t*)&color);
} else {
_argbBuffer[(y - _overscan.Top)*_overscan.GetScreenWidth() + (x - _overscan.Left)] = color;
}
}
__forceinline void BlendColors(uint8_t output[4], uint8_t input[4])
{
uint8_t alpha = input[3] + 1;
uint8_t invertedAlpha = 256 - input[3];
output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8);
output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8);
output[2] = (uint8_t)((alpha * input[2] + invertedAlpha * output[2]) >> 8);
output[3] = 0xFF;
}
public:
DrawCommand(int frameCount)
{
_frameCount = frameCount > 0 ? frameCount : -1;
}
void Draw(uint32_t* argbBuffer, OverscanDimensions &overscan)
{
_argbBuffer = argbBuffer;
_overscan = overscan;
InternalDraw();
_frameCount--;
}
bool Expired()
{
return _frameCount == 0;
}
};

42
Core/DrawLineCommand.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "stdafx.h"
#include "DrawCommand.h"
class DrawLineCommand : public DrawCommand
{
private:
int _x, _y, _x2, _y2, _color;
protected:
void InternalDraw()
{
int xDiff = _x2 - _x;
int yDiff = _y2 - _y;
if(xDiff == 0) {
for(int i = _y; i <= _y2; i++) {
DrawPixel(_x, i, _color);
}
} else {
double deltaErr = abs((double)yDiff / (double)xDiff);
double error = deltaErr - 0.5;
int y = _y;
for(int x = _x; x <= _x2; x++) {
DrawPixel(x, y, _color);
error += deltaErr;
if(error >= 0.5) {
y++;
error -= 1.0;
}
}
}
}
public:
DrawLineCommand(int x, int y, int x2, int y2, int color, int frameCount) :
_x(x), _y(y), _x2(x2), _y2(y2), _color(color), DrawCommand(frameCount)
{
if(!(_color & 0xFF000000)) {
_color |= 0xFF000000;
}
}
};

24
Core/DrawPixelCommand.h Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "stdafx.h"
#include "DrawCommand.h"
class DrawPixelCommand : public DrawCommand
{
private:
int _x, _y, _color;
protected:
void InternalDraw()
{
DrawPixel(_x, _y, _color);
}
public:
DrawPixelCommand(int x, int y, int color, int frameCount) :
_x(x), _y(y), _color(color), DrawCommand(frameCount)
{
if(!(_color & 0xFF000000)) {
_color |= 0xFF000000;
}
}
};

View file

@ -0,0 +1,40 @@
#pragma once
#include "stdafx.h"
#include "DrawCommand.h"
class DrawRectangleCommand : public DrawCommand
{
private:
int _x, _y, _width, _height, _color;
bool _fill;
protected:
void InternalDraw()
{
if(_fill) {
for(int j = 0; j < _height; j++) {
for(int i = 0; i < _width; i++) {
DrawPixel(_x + i, _y + j, _color);
}
}
} else {
for(int i = 0; i < _width; i++) {
DrawPixel(_x + i, _y, _color);
DrawPixel(_x + i, _y + _height - 1, _color);
}
for(int i = 0; i < _height; i++) {
DrawPixel(_x, _y + i, _color);
DrawPixel(_x + _width - 1, _y + i, _color);
}
}
}
public:
DrawRectangleCommand(int x, int y, int width, int height, int color, bool fill, int frameCount) :
_x(x), _y(y), _width(width), _height(height), _color(color), _fill(fill), DrawCommand(frameCount)
{
if(!(_color & 0xFF000000)) {
_color |= 0xFF000000;
}
}
};

163
Core/DrawStringCommand.h Normal file
View file

@ -0,0 +1,163 @@
#pragma once
#include "stdafx.h"
#include "DrawCommand.h"
class DrawStringCommand : public DrawCommand
{
private:
int _x, _y, _color, _backColor;
string _text;
//Taken from FCEUX's LUA code
const int _tabSpace = 4;
const uint8_t _font[792] = {
6, 0, 0, 0, 0, 0, 0, 0, // 0x20 - Spacebar
3, 64, 64, 64, 64, 64, 0, 64,
5, 80, 80, 80, 0, 0, 0, 0,
6, 80, 80,248, 80,248, 80, 80,
6, 32,120,160,112, 40,240, 32,
6, 64,168, 80, 32, 80,168, 16,
6, 96,144,160, 64,168,144,104,
3, 64, 64, 0, 0, 0, 0, 0,
4, 32, 64, 64, 64, 64, 64, 32,
4, 64, 32, 32, 32, 32, 32, 64,
6, 0, 80, 32,248, 32, 80, 0,
6, 0, 32, 32,248, 32, 32, 0,
3, 0, 0, 0, 0, 0, 64,128,
5, 0, 0, 0,240, 0, 0, 0,
3, 0, 0, 0, 0, 0, 0, 64,
5, 16, 16, 32, 32, 32, 64, 64,
6,112,136,136,136,136,136,112, // 0x30 - 0
6, 32, 96, 32, 32, 32, 32, 32,
6,112,136, 8, 48, 64,128,248,
6,112,136, 8, 48, 8,136,112,
6, 16, 48, 80,144,248, 16, 16,
6,248,128,128,240, 8, 8,240,
6, 48, 64,128,240,136,136,112,
6,248, 8, 16, 16, 32, 32, 32,
6,112,136,136,112,136,136,112,
6,112,136,136,120, 8, 16, 96,
3, 0, 0, 64, 0, 0, 64, 0,
3, 0, 0, 64, 0, 0, 64,128,
4, 0, 32, 64,128, 64, 32, 0,
5, 0, 0,240, 0,240, 0, 0,
4, 0,128, 64, 32, 64,128, 0,
6,112,136, 8, 16, 32, 0, 32, // 0x3F - ?
6,112,136,136,184,176,128,112, // 0x40 - @
6,112,136,136,248,136,136,136, // 0x41 - A
6,240,136,136,240,136,136,240,
6,112,136,128,128,128,136,112,
6,224,144,136,136,136,144,224,
6,248,128,128,240,128,128,248,
6,248,128,128,240,128,128,128,
6,112,136,128,184,136,136,120,
6,136,136,136,248,136,136,136,
4,224, 64, 64, 64, 64, 64,224,
6, 8, 8, 8, 8, 8,136,112,
6,136,144,160,192,160,144,136,
6,128,128,128,128,128,128,248,
6,136,216,168,168,136,136,136,
6,136,136,200,168,152,136,136,
7, 48, 72,132,132,132, 72, 48,
6,240,136,136,240,128,128,128,
6,112,136,136,136,168,144,104,
6,240,136,136,240,144,136,136,
6,112,136,128,112, 8,136,112,
6,248, 32, 32, 32, 32, 32, 32,
6,136,136,136,136,136,136,112,
6,136,136,136, 80, 80, 32, 32,
6,136,136,136,136,168,168, 80,
6,136,136, 80, 32, 80,136,136,
6,136,136, 80, 32, 32, 32, 32,
6,248, 8, 16, 32, 64,128,248,
3,192,128,128,128,128,128,192,
5, 64, 64, 32, 32, 32, 16, 16,
3,192, 64, 64, 64, 64, 64,192,
4, 64,160, 0, 0, 0, 0, 0,
6, 0, 0, 0, 0, 0, 0,248,
3,128, 64, 0, 0, 0, 0, 0,
5, 0, 0, 96, 16,112,144,112, // 0x61 - a
5,128,128,224,144,144,144,224,
5, 0, 0,112,128,128,128,112,
5, 16, 16,112,144,144,144,112,
5, 0, 0, 96,144,240,128,112,
5, 48, 64,224, 64, 64, 64, 64,
5, 0,112,144,144,112, 16,224,
5,128,128,224,144,144,144,144,
2,128, 0,128,128,128,128,128,
4, 32, 0, 32, 32, 32, 32,192,
5,128,128,144,160,192,160,144,
2,128,128,128,128,128,128,128,
6, 0, 0,208,168,168,168,168,
5, 0, 0,224,144,144,144,144,
5, 0, 0, 96,144,144,144, 96,
5, 0,224,144,144,224,128,128,
5, 0,112,144,144,112, 16, 16,
5, 0, 0,176,192,128,128,128,
5, 0, 0,112,128, 96, 16,224,
4, 64, 64,224, 64, 64, 64, 32,
5, 0, 0,144,144,144,144,112,
5, 0, 0,144,144,144,160,192,
6, 0, 0,136,136,168,168, 80,
5, 0, 0,144,144, 96,144,144,
5, 0,144,144,144,112, 16, 96,
5, 0, 0,240, 32, 64,128,240,
4, 32, 64, 64,128, 64, 64, 32,
3, 64, 64, 64, 64, 64, 64, 64,
4,128, 64, 64, 32, 64, 64,128,
6, 0,104,176, 0, 0, 0, 0
};
int GetCharNumber(char ch)
{
ch -= 32;
return (ch < 0 || ch > 98) ? 0 : ch;
}
int GetCharWidth(char ch)
{
return _font[GetCharNumber(ch) * 8];
}
protected:
void InternalDraw()
{
int x = _x;
int y = _y;
for(char c : _text) {
if(c == '\n') {
x = _x;
y += 9;
} else if(c == '\t') {
x += (_tabSpace - (((x - _x) / 8) % _tabSpace)) * 8;
} else {
int ch = GetCharNumber(c);
int width = GetCharWidth(c);
int rowOffset = (c == 'y' || c == 'g' || c == 'p' || c == 'q') ? 1 : 0;
for(int j = 0; j < 8; j++) {
uint8_t rowData = (j == 7 && rowOffset == 0 || j == 0 && rowOffset == 1) ? 0 : _font[ch * 8 + 1 + j - rowOffset];
for(int i = 0; i < width; i++) {
int drawFg = (rowData >> (7 - i)) & 0x01;
DrawPixel(x + i, y + j, drawFg ? _color : _backColor);
}
}
for(int i = 0; i < width; i++) {
DrawPixel(x + i, y - 1, _backColor);
}
x += width;
}
}
}
public:
DrawStringCommand(int x, int y, string text, int color, int backColor, int frameCount) :
_x(x), _y(y), _text(text), _color(color), _backColor(backColor), DrawCommand(frameCount)
{
if(!(_color & 0xFF000000)) {
_color |= 0xFF000000;
}
if(!(_backColor & 0xFF000000)) {
_backColor |= 0xFF000000;
}
}
};

View file

@ -359,7 +359,7 @@ int32_t ExpressionEvaluator::Evaluate(vector<int> &rpnList, DebugState &state, E
case EvalOperators::BinaryNot: token = ~right; break; case EvalOperators::BinaryNot: token = ~right; break;
case EvalOperators::LogicalNot: token = !right; break; case EvalOperators::LogicalNot: token = !right; break;
case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right); break; case EvalOperators::Bracket: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right); break;
case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right) | (_debugger->GetMemoryDumper()->GetMemoryValue(DebugMemoryType::CpuMemory, right+1) << 8); break; case EvalOperators::Braces: token = _debugger->GetMemoryDumper()->GetMemoryValueWord(DebugMemoryType::CpuMemory, right); break;
default: throw std::runtime_error("Invalid operator"); default: throw std::runtime_error("Invalid operator");
} }
} }

View file

@ -67,7 +67,7 @@ void HdAudioDevice::ProcessControlFlags(uint8_t flags)
void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges) void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges)
{ {
bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange); bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange) == (int)HdPackOptions::AlternateRegisterRange;
ranges.SetAllowOverride(); ranges.SetAllowOverride();
if(useAlternateRegisters) { if(useAlternateRegisters) {

825
Core/LuaApi.cpp Normal file
View file

@ -0,0 +1,825 @@
#include "stdafx.h"
#include "LuaApi.h"
#include "../Utilities/HexUtilities.h"
#include "../Lua/lua.hpp"
#include "LuaCallHelper.h"
#include "Debugger.h"
#include "MemoryDumper.h"
#include "MessageManager.h"
#include "ScriptingContext.h"
#include "DebugHud.h"
#include "VideoDecoder.h"
#include "RewindManager.h"
#include "SaveStateManager.h"
#include "Console.h"
#include "IKeyManager.h"
#include "ControlManager.h"
#include "StandardController.h"
#include "PPU.h"
#include "CheatManager.h"
#define lua_pushintvalue(name, value) lua_pushliteral(lua, #name); lua_pushinteger(lua, (int)value); lua_settable(lua, -3);
#define lua_pushboolvalue(name, value) lua_pushliteral(lua, #name); lua_pushboolean(lua, (int)value); lua_settable(lua, -3);
#define lua_starttable(name) lua_pushliteral(lua, name); lua_newtable(lua);
#define lua_endtable() lua_settable(lua, -3);
#define lua_readint(name, dest) lua_getfield(lua, -1, #name); dest = l.ReadInteger();
#define lua_readbool(name, dest) lua_getfield(lua, -1, #name); dest = l.ReadBool();
#define error(text) luaL_error(lua, text); return 0;
#define errorCond(cond, text) if(cond) { luaL_error(lua, text); return 0; }
#define checkparams() if(!l.CheckParamCount()) { return 0; }
#define checkminparams(x) if(!l.CheckParamCount(x)) { return 0; }
#define checkstartframe() if(!_context->CheckInStartFrameEvent()) { error("This function cannot be called outside StartFrame event callback"); return 0; }
enum class ExecuteCountType
{
CpuCycles = 0,
PpuCycles = 1,
CpuInstructions = 2
};
Debugger* LuaApi::_debugger = nullptr;
MemoryDumper* LuaApi::_memoryDumper = nullptr;
ScriptingContext* LuaApi::_context = nullptr;
void LuaApi::SetContext(ScriptingContext * context)
{
_context = context;
}
void LuaApi::RegisterDebugger(Debugger * debugger)
{
_debugger = debugger;
_memoryDumper = debugger->GetMemoryDumper().get();
}
int LuaApi::GetLibrary(lua_State *lua)
{
static const luaL_Reg apilib[] = {
{ "read", LuaApi::ReadMemory },
{ "write", LuaApi::WriteMemory },
{ "debugRead", LuaApi::DebugReadMemory },
{ "debugWrite", LuaApi::DebugWriteMemory },
{ "readWord", LuaApi::ReadMemoryWord },
{ "writeWord", LuaApi::WriteMemoryWord },
{ "debugReadWord", LuaApi::DebugReadMemoryWord },
{ "debugWriteWord", LuaApi::DebugWriteMemoryWord },
{ "revertPrgChrChanges", LuaApi::RevertPrgChrChanges },
{ "addMemoryCallback", LuaApi::RegisterMemoryCallback },
{ "removeMemoryCallback", LuaApi::UnregisterMemoryCallback },
{ "addEventCallback", LuaApi::RegisterEventCallback },
{ "removeEventCallback", LuaApi::UnregisterEventCallback },
{ "drawString", LuaApi::DrawString },
{ "drawPixel", LuaApi::DrawPixel },
{ "drawLine", LuaApi::DrawLine },
{ "drawRectangle", LuaApi::DrawRectangle },
{ "clearScreen", LuaApi::ClearScreen },
{ "getPixel", LuaApi::GetPixel },
{ "getMouseState", LuaApi::GetMouseState },
{ "log", LuaApi::Log },
{ "displayMessage", LuaApi::DisplayMessage },
{ "reset", LuaApi::Reset },
{ "breakExecution", LuaApi::Break },
{ "resume", LuaApi::Resume },
{ "execute", LuaApi::Execute },
{ "rewind", LuaApi::Rewind },
{ "takeScreenshot", LuaApi::TakeScreenshot },
{ "saveSavestate", LuaApi::SaveSavestate },
{ "loadSavestate", LuaApi::LoadSavestate },
{ "getInput", LuaApi::GetInput },
{ "setInput", LuaApi::SetInput },
{ "addCheat", LuaApi::AddCheat },
{ "clearCheats", LuaApi::ClearCheats },
{ "setState", LuaApi::SetState },
{ "getState", LuaApi::GetState },
{ NULL,NULL }
};
luaL_newlib(lua, apilib);
//Expose DebugMemoryType enum as "memory.type"
lua_pushliteral(lua, "memType");
lua_newtable(lua);
lua_pushintvalue(cpu, DebugMemoryType::CpuMemory);
lua_pushintvalue(ppu, DebugMemoryType::PpuMemory);
lua_pushintvalue(palette, DebugMemoryType::PaletteMemory);
lua_pushintvalue(oam, DebugMemoryType::SpriteMemory);
lua_pushintvalue(secondaryOam, DebugMemoryType::SecondarySpriteMemory);
lua_pushintvalue(prgRom, DebugMemoryType::PrgRom);
lua_pushintvalue(chrRom, DebugMemoryType::ChrRom);
lua_pushintvalue(chrRam, DebugMemoryType::ChrRam);
lua_pushintvalue(workRam, DebugMemoryType::WorkRam);
lua_pushintvalue(saveRam, DebugMemoryType::SaveRam);
lua_settable(lua, -3);
lua_pushliteral(lua, "memCallbackType");
lua_newtable(lua);
lua_pushintvalue(cpuRead, CallbackType::CpuRead);
lua_pushintvalue(cpuWrite, CallbackType::CpuWrite);
lua_pushintvalue(cpuExec, CallbackType::CpuExec);
lua_pushintvalue(ppuRead, CallbackType::PpuRead);
lua_pushintvalue(ppuWrite, CallbackType::PpuWrite);
lua_settable(lua, -3);
lua_pushliteral(lua, "eventCallbackType");
lua_newtable(lua);
lua_pushintvalue(power, EventType::Power);
lua_pushintvalue(reset, EventType::Reset);
lua_pushintvalue(nmi, EventType::Nmi);
lua_pushintvalue(irq, EventType::Irq);
lua_pushintvalue(startFrame, EventType::StartFrame);
lua_pushintvalue(endFrame, EventType::EndFrame);
lua_pushintvalue(codeBreak, EventType::CodeBreak);
lua_settable(lua, -3);
return 1;
}
int LuaApi::ReadMemory(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(address < 0, "address must be >= 0");
l.Return(_memoryDumper->GetMemoryValue(type, address, false));
return l.ReturnCount();
}
int LuaApi::WriteMemory(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int value = l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(value > 255 || value < -128, "value out of range");
errorCond(address < 0, "address must be >= 0");
_memoryDumper->SetMemoryValue(type, address, value, false);
return l.ReturnCount();
}
int LuaApi::DebugReadMemory(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(address < 0, "address must be >= 0");
l.Return(_memoryDumper->GetMemoryValue(type, address));
return l.ReturnCount();
}
int LuaApi::DebugWriteMemory(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int value = l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(value > 255 || value < -128, "value out of range");
errorCond(address < 0, "address must be >= 0");
_memoryDumper->SetMemoryValue(type, address, value);
return l.ReturnCount();
}
int LuaApi::ReadMemoryWord(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(address < 0, "address must be >= 0");
l.Return(_memoryDumper->GetMemoryValueWord(type, address, false));
return l.ReturnCount();
}
int LuaApi::WriteMemoryWord(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int value = l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(value > 65535 || value < -32768, "value out of range");
errorCond(address < 0, "address must be >= 0");
_memoryDumper->SetMemoryValueWord(type, address, value, false);
return l.ReturnCount();
}
int LuaApi::DebugReadMemoryWord(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(address < 0, "address must be >= 0");
l.Return(_memoryDumper->GetMemoryValueWord(type, address));
return l.ReturnCount();
}
int LuaApi::DebugWriteMemoryWord(lua_State *lua)
{
LuaCallHelper l(lua);
DebugMemoryType type = (DebugMemoryType)l.ReadInteger();
int value = l.ReadInteger();
int address = l.ReadInteger();
checkparams();
errorCond(value > 65535 || value < -32768, "value out of range");
errorCond(address < 0, "address must be >= 0");
_memoryDumper->SetMemoryValueWord(type, address, value);
return l.ReturnCount();
}
int LuaApi::RevertPrgChrChanges(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
_debugger->RevertPrgChrChanges();
return l.ReturnCount();
}
int LuaApi::RegisterMemoryCallback(lua_State *lua)
{
LuaCallHelper l(lua);
int endAddr = l.ReadInteger();
int startAddr = l.ReadInteger();
CallbackType type = (CallbackType)l.ReadInteger();
int reference = l.GetReference();
checkparams();
errorCond(startAddr > endAddr, "start address must be <= end address");
errorCond(type < CallbackType::CpuRead || type > CallbackType::PpuWrite, "the specified type is invalid");
errorCond(reference == LUA_NOREF, "the specified function could not be found");
_context->RegisterMemoryCallback(type, startAddr, endAddr, reference);
_context->Log("Registered memory callback from $" + HexUtilities::ToHex((uint32_t)startAddr) + " to $" + HexUtilities::ToHex((uint32_t)endAddr));
l.Return(reference);
return l.ReturnCount();
}
int LuaApi::UnregisterMemoryCallback(lua_State *lua)
{
LuaCallHelper l(lua);
int endAddr = l.ReadInteger();
int startAddr = l.ReadInteger();
CallbackType type = (CallbackType)l.ReadInteger();
int reference = l.ReadInteger();
checkparams();
errorCond(startAddr > endAddr, "start address must be <= end address");
errorCond(type < CallbackType::CpuRead || type > CallbackType::PpuWrite, "the specified type is invalid");
errorCond(reference == LUA_NOREF, "function reference is invalid");
_context->UnregisterMemoryCallback(type, startAddr, endAddr, reference);
return l.ReturnCount();
}
int LuaApi::RegisterEventCallback(lua_State *lua)
{
LuaCallHelper l(lua);
EventType type = (EventType)l.ReadInteger();
int reference = l.GetReference();
checkparams();
errorCond(type < EventType::Power || type > EventType::CodeBreak, "the specified type is invalid");
errorCond(reference == LUA_NOREF, "the specified function could not be found");
_context->RegisterEventCallback(type, reference);
return l.ReturnCount();
}
int LuaApi::UnregisterEventCallback(lua_State *lua)
{
LuaCallHelper l(lua);
EventType type = (EventType)l.ReadInteger();
int reference = l.ReadInteger();
checkparams();
errorCond(type < EventType::Power || type > EventType::CodeBreak, "the specified type is invalid");
errorCond(reference == LUA_NOREF, "function reference is invalid");
_context->UnregisterEventCallback(type, reference);
return l.ReturnCount();
}
int LuaApi::DrawString(lua_State *lua)
{
LuaCallHelper l(lua);
l.ForceParamCount(6);
int frameCount = l.ReadInteger(1);
int backColor = l.ReadInteger(0);
int color = l.ReadInteger(0xFFFFFFFF);
string text = l.ReadString();
int y = l.ReadInteger();
int x = l.ReadInteger();
checkminparams(3);
DebugHud::GetInstance()->DrawString(x, y, text, color, backColor, frameCount);
return l.ReturnCount();
}
int LuaApi::DrawLine(lua_State *lua)
{
LuaCallHelper l(lua);
int frameCount = l.ReadInteger(1);
int color = l.ReadInteger(0xFFFFFFFF);
int y2 = l.ReadInteger();
int x2 = l.ReadInteger();
int y = l.ReadInteger();
int x = l.ReadInteger();
checkminparams(4);
DebugHud::GetInstance()->DrawLine(x, y, x2, y2, color, frameCount);
return l.ReturnCount();
}
int LuaApi::DrawPixel(lua_State *lua)
{
LuaCallHelper l(lua);
int frameCount = l.ReadInteger(1);
int color = l.ReadInteger();
int y = l.ReadInteger();
int x = l.ReadInteger();
checkminparams(3);
DebugHud::GetInstance()->DrawPixel(x, y, color, frameCount);
return l.ReturnCount();
}
int LuaApi::DrawRectangle(lua_State *lua)
{
LuaCallHelper l(lua);
int frameCount = l.ReadInteger(1);
bool fill = l.ReadBool(false);
int color = l.ReadInteger(0xFFFFFFFF);
int height = l.ReadInteger();
int width = l.ReadInteger();
int y = l.ReadInteger();
int x = l.ReadInteger();
checkminparams(4);
errorCond(height <= 0, "height must be >= 1");
errorCond(width <= 0, "width must be >= 1");
DebugHud::GetInstance()->DrawRectangle(x, y, width, height, color, fill, frameCount);
return l.ReturnCount();
}
int LuaApi::ClearScreen(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
DebugHud::GetInstance()->ClearScreen();
return l.ReturnCount();
}
int LuaApi::GetPixel(lua_State *lua)
{
LuaCallHelper l(lua);
int y = l.ReadInteger();
int x = l.ReadInteger();
checkparams();
errorCond(x < 0 || x > 255 || y < 0 || y > 239, "invalid x,y coordinates (must be between 0-255, 0-239)");
//Ignores intensify & grayscale bits
l.Return(EmulationSettings::GetRgbPalette()[PPU::GetPixel(x, y) & 0x3F]);
return l.ReturnCount();
}
int LuaApi::GetMouseState(lua_State *lua)
{
LuaCallHelper l(lua);
MousePosition pos = ControlManager::GetMousePosition();
checkparams();
lua_newtable(lua);
lua_pushintvalue(x, pos.X);
lua_pushintvalue(y, pos.Y);
lua_pushboolvalue(left, ControlManager::IsMouseButtonPressed(MouseButton::LeftButton));
lua_pushboolvalue(middle, ControlManager::IsMouseButtonPressed(MouseButton::MiddleButton));
lua_pushboolvalue(right, ControlManager::IsMouseButtonPressed(MouseButton::RightButton));
return 1;
}
int LuaApi::Log(lua_State *lua)
{
LuaCallHelper l(lua);
string text = l.ReadString();
checkparams();
_context->Log(text);
return l.ReturnCount();
}
int LuaApi::DisplayMessage(lua_State *lua)
{
LuaCallHelper l(lua);
string text = l.ReadString();
string category = l.ReadString();
checkparams();
MessageManager::DisplayMessage(category, text);
return l.ReturnCount();
}
int LuaApi::Reset(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
Console::RequestReset();
return l.ReturnCount();
}
int LuaApi::Break(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
_debugger->Step(1);
return l.ReturnCount();
}
int LuaApi::Resume(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
_debugger->Run();
return l.ReturnCount();
}
int LuaApi::Execute(lua_State *lua)
{
LuaCallHelper l(lua);
ExecuteCountType type = (ExecuteCountType)l.ReadInteger();
int count = l.ReadInteger();
checkparams();
errorCond(count <= 0, "count must be >= 1");
errorCond(type < ExecuteCountType::CpuCycles || type > ExecuteCountType::CpuInstructions, "type is invalid");
switch(type) {
case ExecuteCountType::CpuCycles: _debugger->StepCycles(count); break;
case ExecuteCountType::PpuCycles: _debugger->PpuStep(count); break;
case ExecuteCountType::CpuInstructions: _debugger->Step(count); break;
}
return l.ReturnCount();
}
int LuaApi::Rewind(lua_State *lua)
{
LuaCallHelper l(lua);
int seconds = l.ReadInteger();
checkparams();
checkstartframe();
errorCond(seconds <= 0, "seconds must be >= 1");
RewindManager::RewindSeconds(seconds);
return l.ReturnCount();
}
int LuaApi::TakeScreenshot(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
stringstream ss;
VideoDecoder::GetInstance()->TakeScreenshot(ss);
l.Return(ss.str());
return l.ReturnCount();
}
int LuaApi::SaveSavestate(lua_State *lua)
{
LuaCallHelper l(lua);
checkstartframe();
stringstream ss;
SaveStateManager::SaveState(ss);
l.Return(ss.str());
return l.ReturnCount();
}
int LuaApi::LoadSavestate(lua_State *lua)
{
LuaCallHelper l(lua);
string savestate = l.ReadString();
checkparams();
checkstartframe();
stringstream ss;
ss << savestate;
l.Return(SaveStateManager::LoadState(ss));
return l.ReturnCount();
}
int LuaApi::GetInput(lua_State *lua)
{
LuaCallHelper l(lua);
int port = l.ReadInteger();
checkparams();
errorCond(port < 0 || port > 3, "Invalid port number - must be between 0 to 3");
shared_ptr<StandardController> controller = std::dynamic_pointer_cast<StandardController>(ControlManager::GetControlDevice(port));
errorCond(controller == nullptr, "Input port must be connected to a standard controller");
ButtonState state = controller->GetButtonState();
lua_newtable(lua);
lua_pushboolvalue(a, state.A);
lua_pushboolvalue(b, state.B);
lua_pushboolvalue(start, state.Start);
lua_pushboolvalue(select, state.Select);
lua_pushboolvalue(up, state.Up);
lua_pushboolvalue(down, state.Down);
lua_pushboolvalue(left, state.Left);
lua_pushboolvalue(right, state.Right);
return 1;
}
int LuaApi::SetInput(lua_State *lua)
{
lua_settop(lua, 2);
luaL_checktype(lua, 2, LUA_TTABLE);
lua_getfield(lua, 2, "a");
lua_getfield(lua, 2, "b");
lua_getfield(lua, 2, "start");
lua_getfield(lua, 2, "select");
lua_getfield(lua, 2, "up");
lua_getfield(lua, 2, "down");
lua_getfield(lua, 2, "left");
lua_getfield(lua, 2, "right");
LuaCallHelper l(lua);
ButtonState buttonState;
buttonState.Right = l.ReadBool();
buttonState.Left = l.ReadBool();
buttonState.Down = l.ReadBool();
buttonState.Up = l.ReadBool();
buttonState.Select = l.ReadBool();
buttonState.Start = l.ReadBool();
buttonState.B = l.ReadBool();
buttonState.A = l.ReadBool();
lua_pop(lua, 1);
int port = l.ReadInteger();
_debugger->SetInputOverride(port, buttonState.ToByte());
errorCond(port < 0 || port > 3, "Invalid port number - must be between 0 to 3");
return l.ReturnCount();
}
int LuaApi::AddCheat(lua_State *lua)
{
LuaCallHelper l(lua);
string gamegenieCode = l.ReadString();
checkparams();
errorCond(gamegenieCode.length() != 6 && gamegenieCode.length() != 8, "Game genie code must be 6 or 8 characters long");
errorCond(gamegenieCode.find_first_not_of("APZLGITYEOXUKSVN", 0) != string::npos, "Game genie code may only contain these characters: AEGIKLNOPSTUVXYZ");
CheatManager::GetInstance()->AddGameGenieCode(gamegenieCode);
return l.ReturnCount();
}
int LuaApi::ClearCheats(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
CheatManager::GetInstance()->ClearCodes();
return l.ReturnCount();
}
int LuaApi::SetState(lua_State *lua)
{
LuaCallHelper l(lua);
lua_settop(lua, 1);
luaL_checktype(lua, -1, LUA_TTABLE);
DebugState state;
lua_getfield(lua, -1, "cpu");
luaL_checktype(lua, -1, LUA_TTABLE);
lua_readint(a, state.CPU.A);
lua_readint(cycleCount, state.CPU.CycleCount);
lua_readint(irqFlag, state.CPU.IRQFlag);
lua_readbool(nmiFlag, state.CPU.NMIFlag);
lua_readint(pc, state.CPU.PC);
lua_readint(ps, state.CPU.PS);
lua_readint(sp, state.CPU.SP);
lua_readint(x, state.CPU.X);
lua_readint(y, state.CPU.Y);
lua_pop(lua, 1);
lua_getfield(lua, -1, "ppu");
luaL_checktype(lua, -1, LUA_TTABLE);
lua_readint(cycle, state.PPU.Cycle);
lua_readint(frameCount, state.PPU.FrameCount);
lua_readint(scanline, state.PPU.Scanline);
lua_getfield(lua, -1, "control");
luaL_checktype(lua, -1, LUA_TTABLE);
lua_readbool(backgroundEnabled, state.PPU.ControlFlags.BackgroundEnabled);
lua_readbool(backgroundMask, state.PPU.ControlFlags.BackgroundMask);
lua_readint(backgroundPatternAddr, state.PPU.ControlFlags.BackgroundPatternAddr);
lua_readbool(grayscale, state.PPU.ControlFlags.Grayscale);
lua_readbool(intensifyBlue, state.PPU.ControlFlags.IntensifyBlue);
lua_readbool(intensifyGreen, state.PPU.ControlFlags.IntensifyGreen);
lua_readbool(intensifyRed, state.PPU.ControlFlags.IntensifyRed);
lua_readbool(largeSprites, state.PPU.ControlFlags.LargeSprites);
lua_readbool(spriteMask, state.PPU.ControlFlags.SpriteMask);
lua_readint(spritePatternAddr, state.PPU.ControlFlags.SpritePatternAddr);
lua_readbool(spritesEnabled, state.PPU.ControlFlags.SpritesEnabled);
lua_readbool(nmiOnVBlank, state.PPU.ControlFlags.VBlank);
lua_readbool(verticalWrite, state.PPU.ControlFlags.VerticalWrite);
lua_pop(lua, 1);
lua_getfield(lua, -1, "status");
luaL_checktype(lua, -1, LUA_TTABLE);
lua_readbool(sprite0Hit, state.PPU.StatusFlags.Sprite0Hit);
lua_readbool(spriteOverflow, state.PPU.StatusFlags.SpriteOverflow);
lua_readbool(verticalBlank, state.PPU.StatusFlags.VerticalBlank);
lua_pop(lua, 1);
lua_getfield(lua, -1, "state");
luaL_checktype(lua, -1, LUA_TTABLE);
lua_readint(control, state.PPU.State.Control);
lua_readint(highBitShift, state.PPU.State.HighBitShift);
lua_readint(lowBitShift, state.PPU.State.LowBitShift);
lua_readint(mask, state.PPU.State.Mask);
lua_readint(spriteRamAddr, state.PPU.State.SpriteRamAddr);
lua_readint(status, state.PPU.State.Status);
lua_readint(tmpVideoRamAddr, state.PPU.State.TmpVideoRamAddr);
lua_readint(videoRamAddr, state.PPU.State.VideoRamAddr);
lua_readbool(writeToggle, state.PPU.State.WriteToggle);
lua_readint(xScroll, state.PPU.State.XScroll);
lua_pop(lua, 1);
lua_pop(lua, 1);
_debugger->SetState(state);
return 0;
}
int LuaApi::GetState(lua_State *lua)
{
LuaCallHelper l(lua);
checkparams();
DebugState state;
_debugger->GetState(&state, true);
lua_newtable(lua);
lua_pushintvalue(region, state.Model);
lua_starttable("cpu");
lua_pushintvalue(a, state.CPU.A);
lua_pushintvalue(cycleCount, state.CPU.CycleCount);
lua_pushintvalue(irqFlag, state.CPU.IRQFlag);
lua_pushboolvalue(nmiFlag, state.CPU.NMIFlag);
lua_pushintvalue(pc, state.CPU.PC);
lua_pushintvalue(status, state.CPU.PS);
lua_pushintvalue(sp, state.CPU.SP);
lua_pushintvalue(x, state.CPU.X);
lua_pushintvalue(y, state.CPU.Y);
lua_endtable();
lua_starttable("ppu");
lua_pushintvalue(cycle, state.PPU.Cycle);
lua_pushintvalue(frameCount, state.PPU.FrameCount);
lua_pushintvalue(scanline, state.PPU.Scanline);
lua_starttable("control");
lua_pushboolvalue(backgroundEnabled, state.PPU.ControlFlags.BackgroundEnabled);
lua_pushboolvalue(backgroundMask, state.PPU.ControlFlags.BackgroundMask);
lua_pushintvalue(backgroundPatternAddr, state.PPU.ControlFlags.BackgroundPatternAddr);
lua_pushboolvalue(grayscale, state.PPU.ControlFlags.Grayscale);
lua_pushboolvalue(intensifyBlue, state.PPU.ControlFlags.IntensifyBlue);
lua_pushboolvalue(intensifyGreen, state.PPU.ControlFlags.IntensifyGreen);
lua_pushboolvalue(intensifyRed, state.PPU.ControlFlags.IntensifyRed);
lua_pushboolvalue(largeSprites, state.PPU.ControlFlags.LargeSprites);
lua_pushboolvalue(spriteMask, state.PPU.ControlFlags.SpriteMask);
lua_pushintvalue(spritePatternAddr, state.PPU.ControlFlags.SpritePatternAddr);
lua_pushboolvalue(spritesEnabled, state.PPU.ControlFlags.SpritesEnabled);
lua_pushboolvalue(nmiOnVBlank, state.PPU.ControlFlags.VBlank);
lua_pushboolvalue(verticalWrite, state.PPU.ControlFlags.VerticalWrite);
lua_endtable();
lua_starttable("status");
lua_pushboolvalue(sprite0Hit, state.PPU.StatusFlags.Sprite0Hit);
lua_pushboolvalue(spriteOverflow, state.PPU.StatusFlags.SpriteOverflow);
lua_pushboolvalue(verticalBlank, state.PPU.StatusFlags.VerticalBlank);
lua_endtable();
lua_starttable("state");
lua_pushintvalue(control, state.PPU.State.Control);
lua_pushintvalue(highBitShift, state.PPU.State.HighBitShift);
lua_pushintvalue(lowBitShift, state.PPU.State.LowBitShift);
lua_pushintvalue(mask, state.PPU.State.Mask);
lua_pushintvalue(spriteRamAddr, state.PPU.State.SpriteRamAddr);
lua_pushintvalue(status, state.PPU.State.Status);
lua_pushintvalue(tmpVideoRamAddr, state.PPU.State.TmpVideoRamAddr);
lua_pushintvalue(videoRamAddr, state.PPU.State.VideoRamAddr);
lua_pushboolvalue(writeToggle, state.PPU.State.WriteToggle);
lua_pushintvalue(xScroll, state.PPU.State.XScroll);
lua_endtable();
lua_endtable(); //end ppu
lua_starttable("cart");
lua_pushintvalue(chrPageCount, state.Cartridge.ChrPageCount);
lua_pushintvalue(chrPageSize, state.Cartridge.ChrPageSize);
lua_pushintvalue(chrRamSize, state.Cartridge.ChrRamSize);
lua_pushintvalue(chrRomSize, state.Cartridge.ChrRomSize);
lua_pushintvalue(prgPageCount, state.Cartridge.PrgPageCount);
lua_pushintvalue(prgPageSize, state.Cartridge.PrgPageSize);
lua_pushintvalue(prgRomSize, state.Cartridge.PrgRomSize);
lua_starttable("selectedPrgPages");
for(int i = 0, max = 0x8000 / state.Cartridge.PrgPageSize; i < max; i++) {
lua_pushinteger(lua, i + 1);
lua_pushinteger(lua, (int32_t)state.Cartridge.PrgSelectedPages[i]);
lua_settable(lua, -3);
}
lua_endtable();
lua_starttable("selectedChrPages");
for(int i = 0, max = 0x2000 / state.Cartridge.ChrPageSize; i < max; i++) {
lua_pushinteger(lua, i + 1);
lua_pushinteger(lua, (int32_t)state.Cartridge.ChrSelectedPages[i]);
lua_settable(lua, -3);
}
lua_endtable();
lua_endtable();
lua_starttable("apu");
lua_starttable("square1");
PushSquareState(lua, state.APU.Square1);
lua_endtable();
lua_starttable("square2");
PushSquareState(lua, state.APU.Square2);
lua_endtable();
lua_starttable("triangle");
lua_pushboolvalue(enabled, state.APU.Triangle.Enabled);
lua_pushintvalue(frequency, state.APU.Triangle.Frequency);
PushLengthCounterState(lua, state.APU.Triangle.LengthCounter);
lua_pushintvalue(outputVolume, state.APU.Triangle.OutputVolume);
lua_pushintvalue(period, state.APU.Triangle.Period);
lua_pushintvalue(sequencePosition, state.APU.Triangle.SequencePosition);
lua_endtable();
lua_starttable("noise");
lua_pushboolvalue(enabled, state.APU.Noise.Enabled);
lua_pushintvalue(frequency, state.APU.Noise.Frequency);
PushEnvelopeState(lua, state.APU.Noise.Envelope);
PushLengthCounterState(lua, state.APU.Noise.LengthCounter);
lua_pushboolvalue(modeFlag, state.APU.Noise.ModeFlag);
lua_pushintvalue(outputVolume, state.APU.Noise.OutputVolume);
lua_pushintvalue(period, state.APU.Noise.Period);
lua_pushintvalue(shiftRegister, state.APU.Noise.ShiftRegister);
lua_endtable();
lua_starttable("dmc");
lua_pushintvalue(bytesRemaining, state.APU.Dmc.BytesRemaining);
lua_pushintvalue(frequency, state.APU.Dmc.Frequency);
lua_pushboolvalue(irqEnabled, state.APU.Dmc.IrqEnabled);
lua_pushboolvalue(loop, state.APU.Dmc.Loop);
lua_pushintvalue(outputVolume, state.APU.Dmc.OutputVolume);
lua_pushintvalue(period, state.APU.Dmc.Period);
lua_pushintvalue(sampleAddr, state.APU.Dmc.SampleAddr);
lua_pushintvalue(sampleLength, state.APU.Dmc.SampleLength);
lua_endtable();
lua_starttable("frameCounter");
lua_pushintvalue(fiveStepMode, state.APU.FrameCounter.FiveStepMode);
lua_pushboolvalue(irqEnabled, state.APU.FrameCounter.IrqEnabled);
lua_pushintvalue(sequencePosition, state.APU.FrameCounter.SequencePosition);
lua_endtable();
lua_endtable(); //end apu
return 1;
}
void LuaApi::PushSquareState(lua_State *lua, ApuSquareState & state)
{
lua_pushintvalue(duty, state.Duty);
lua_pushintvalue(dutyPosition, state.DutyPosition);
lua_pushintvalue(frequency, state.Frequency);
lua_pushintvalue(period, state.Period);
lua_pushboolvalue(sweepEnabled, state.SweepEnabled);
lua_pushboolvalue(sweepNegate, state.SweepNegate);
lua_pushintvalue(sweepPeriod, state.SweepPeriod);
lua_pushintvalue(sweepShift, state.SweepShift);
lua_pushboolvalue(enabled, state.Enabled);
lua_pushintvalue(outputVolume, state.OutputVolume);
PushEnvelopeState(lua, state.Envelope);
PushLengthCounterState(lua, state.LengthCounter);
}
void LuaApi::PushEnvelopeState(lua_State *lua, ApuEnvelopeState & state)
{
lua_starttable("envelope");
lua_pushboolvalue(startFlag, state.StartFlag);
lua_pushboolvalue(loop, state.Loop);
lua_pushboolvalue(constantVolume, state.ConstantVolume);
lua_pushintvalue(divider, state.Divider);
lua_pushintvalue(counter, state.Counter);
lua_pushintvalue(volume, state.Volume);
lua_endtable();
}
void LuaApi::PushLengthCounterState(lua_State *lua, ApuLengthCounterState & state)
{
lua_starttable("lengthCounter");
lua_pushboolvalue(halt, state.Halt);
lua_pushintvalue(counter, state.Counter);
lua_pushintvalue(reloadValue, state.ReloadValue);
lua_endtable();
}

72
Core/LuaApi.h Normal file
View file

@ -0,0 +1,72 @@
#pragma once
#include "stdafx.h"
struct lua_State;
class ScriptingContext;
class Debugger;
class MemoryDumper;
struct ApuSquareState;
struct ApuEnvelopeState;
struct ApuLengthCounterState;
class LuaApi
{
public:
static void SetContext(ScriptingContext *context);
static void RegisterDebugger(Debugger* debugger);
static int GetLibrary(lua_State *lua);
static int ReadMemory(lua_State *lua);
static int WriteMemory(lua_State *lua);
static int DebugReadMemory(lua_State *lua);
static int DebugWriteMemory(lua_State *lua);
static int ReadMemoryWord(lua_State *lua);
static int WriteMemoryWord(lua_State *lua);
static int DebugReadMemoryWord(lua_State *lua);
static int DebugWriteMemoryWord(lua_State *lua);
static int RevertPrgChrChanges(lua_State *lua);
static int RegisterMemoryCallback(lua_State *lua);
static int UnregisterMemoryCallback(lua_State *lua);
static int RegisterEventCallback(lua_State *lua);
static int UnregisterEventCallback(lua_State *lua);
static int DrawString(lua_State *lua);
static int DrawLine(lua_State *lua);
static int DrawPixel(lua_State *lua);
static int DrawRectangle(lua_State *lua);
static int ClearScreen(lua_State *lua);
static int GetPixel(lua_State *lua);
static int GetMouseState(lua_State *lua);
static int Log(lua_State *lua);
static int DisplayMessage(lua_State *lua);
static int Reset(lua_State *lua);
static int Break(lua_State *lua);
static int Resume(lua_State *lua);
static int Execute(lua_State *lua);
static int Rewind(lua_State *lua);
static int TakeScreenshot(lua_State *lua);
static int SaveSavestate(lua_State *lua);
static int LoadSavestate(lua_State *lua);
static int GetInput(lua_State *lua);
static int SetInput(lua_State *lua);
static int AddCheat(lua_State *lua);
static int ClearCheats(lua_State *lua);
static int SetState(lua_State *lua);
static int GetState(lua_State *lua);
private:
static Debugger* _debugger;
static MemoryDumper* _memoryDumper;
static ScriptingContext* _context;
static void PushSquareState(lua_State* lua, ApuSquareState &state);
static void PushEnvelopeState(lua_State* lua, ApuEnvelopeState &state);
static void PushLengthCounterState(lua_State* lua, ApuLengthCounterState &state);
};

117
Core/LuaCallHelper.cpp Normal file
View file

@ -0,0 +1,117 @@
#include "stdafx.h"
#include "LuaCallHelper.h"
LuaCallHelper::LuaCallHelper(lua_State *lua) : _lua(lua)
{
_stackSize = lua_gettop(lua);
}
void LuaCallHelper::ForceParamCount(int paramCount)
{
while(lua_gettop(_lua) < paramCount) {
lua_pushnil(_lua);
}
}
bool LuaCallHelper::CheckParamCount(int minParamCount)
{
if(minParamCount >= 0 && _stackSize < _paramCount && _stackSize >= minParamCount) {
return true;
}
if(_stackSize != _paramCount) {
string message = string("too ") + (_stackSize < _paramCount ? "little" : "many") + " parameters. expected " + std::to_string(_paramCount) + " got " + std::to_string(_stackSize);
luaL_error(_lua, message.c_str());
return false;
}
return true;
}
double LuaCallHelper::ReadDouble()
{
_paramCount++;
double value = 0;
if(lua_isnumber(_lua, -1)) {
value = lua_tonumber(_lua, -1);
}
lua_pop(_lua, 1);
return value;
}
bool LuaCallHelper::ReadBool(bool defaultValue)
{
_paramCount++;
bool value = defaultValue;
if(lua_isboolean(_lua, -1)) {
value = lua_toboolean(_lua, -1) != 0;
} else if(lua_isnumber(_lua, -1)) {
value = lua_tonumber(_lua, -1) != 0;
}
lua_pop(_lua, 1);
return value;
}
uint32_t LuaCallHelper::ReadInteger(uint32_t defaultValue)
{
_paramCount++;
uint32_t value = defaultValue;
if(lua_isinteger(_lua, -1)) {
value = (uint32_t)lua_tointeger(_lua, -1);
} else if(lua_isnumber(_lua, -1)) {
value = (uint32_t)lua_tonumber(_lua, -1);
}
lua_pop(_lua, 1);
return value;
}
string LuaCallHelper::ReadString()
{
_paramCount++;
size_t len;
string str;
if(lua_isstring(_lua, -1)) {
const char* cstr = lua_tolstring(_lua, -1, &len);
str = string(cstr, len);
}
lua_pop(_lua, 1);
return str;
}
int LuaCallHelper::GetReference()
{
_paramCount++;
if(lua_isfunction(_lua, -1)) {
return luaL_ref(_lua, LUA_REGISTRYINDEX);
} else {
lua_pop(_lua, 1);
return LUA_NOREF;
}
}
void LuaCallHelper::Return(bool value)
{
lua_pushboolean(_lua, value);
_returnCount++;
}
void LuaCallHelper::Return(int value)
{
lua_pushinteger(_lua, value);
_returnCount++;
}
void LuaCallHelper::Return(uint32_t value)
{
lua_pushinteger(_lua, value);
_returnCount++;
}
void LuaCallHelper::Return(string value)
{
lua_pushlstring(_lua, value.c_str(), value.size());
_returnCount++;
}
int LuaCallHelper::ReturnCount()
{
return _returnCount;
}

31
Core/LuaCallHelper.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "stdafx.h"
#include "../Lua/lua.hpp"
class LuaCallHelper
{
private:
int _stackSize = 0;
int _paramCount = 0;
int _returnCount = 0;
lua_State* _lua;
public:
LuaCallHelper(lua_State* lua);
void ForceParamCount(int paramCount);
bool CheckParamCount(int minParamCount = -1);
double ReadDouble();
bool ReadBool(bool defaultValue = false);
uint32_t ReadInteger(uint32_t defaultValue = 0);
string ReadString();
int GetReference();
void Return(bool value);
void Return(int value);
void Return(uint32_t value);
void Return(string value);
int ReturnCount();
};

View file

@ -0,0 +1,67 @@
#include "stdafx.h"
#include "../Lua/lua.hpp"
#include "LuaScriptingContext.h"
#include "LuaApi.h"
#include "LuaCallHelper.h"
#include "DebuggerTypes.h"
#include "Debugger.h"
LuaScriptingContext::LuaScriptingContext() { }
LuaScriptingContext::~LuaScriptingContext()
{
if(_lua) {
lua_close(_lua);
_lua = nullptr;
}
}
bool LuaScriptingContext::LoadScript(string scriptContent, Debugger* debugger)
{
int iErr = 0;
_lua = luaL_newstate();
LuaApi::RegisterDebugger(debugger);
LuaApi::SetContext(this);
luaL_openlibs(_lua);
luaL_requiref(_lua, "emu", LuaApi::GetLibrary, 1);
Log("Loading script...");
if((iErr = luaL_loadstring(_lua, scriptContent.c_str())) == 0) {
if((iErr = lua_pcall(_lua, 0, LUA_MULTRET, 0)) == 0) {
//Script loaded properly
Log("Script loaded successfully.");
return true;
}
}
if(lua_isstring(_lua, -1)) {
Log(lua_tostring(_lua, -1));
}
return false;
}
void LuaScriptingContext::CallMemoryCallback(int addr, int value, CallbackType type)
{
LuaApi::SetContext(this);
for(int &ref : _callbacks[(int)type][addr]) {
lua_rawgeti(_lua, LUA_REGISTRYINDEX, ref);
lua_pushinteger(_lua, addr);
lua_pushinteger(_lua, value);
if(lua_pcall(_lua, 2, 0, 0) != 0) {
Log(lua_tostring(_lua, -1));
}
}
}
int LuaScriptingContext::InternalCallEventCallback(EventType type)
{
LuaApi::SetContext(this);
LuaCallHelper l(_lua);
for(int &ref : _eventCallbacks[(int)type]) {
lua_rawgeti(_lua, LUA_REGISTRYINDEX, ref);
if(lua_pcall(_lua, 0, 0, 0) != 0) {
Log(lua_tostring(_lua, -1));
}
}
return l.ReturnCount();
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "stdafx.h"
#include "ScriptingContext.h"
struct lua_State;
class Debugger;
class LuaScriptingContext : public ScriptingContext
{
private:
lua_State* _lua = nullptr;
public:
LuaScriptingContext();
~LuaScriptingContext();
bool LoadScript(string scriptContent, Debugger* debugger);
void CallMemoryCallback(int addr, int value, CallbackType type);
int InternalCallEventCallback(EventType type);
};

View file

@ -126,7 +126,7 @@ void MemoryDumper::SetMemoryValues(DebugMemoryType memoryType, uint32_t address,
} }
} }
void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache) void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache, bool disableSideEffects)
{ {
switch(memoryType) { switch(memoryType) {
case DebugMemoryType::CpuMemory: case DebugMemoryType::CpuMemory:
@ -134,15 +134,15 @@ void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address,
_debugger->GetAbsoluteAddressAndType(address, &info); _debugger->GetAbsoluteAddressAndType(address, &info);
if(info.Address >= 0) { if(info.Address >= 0) {
switch(info.Type) { switch(info.Type) {
case AddressType::InternalRam: SetMemoryValue(DebugMemoryType::InternalRam, info.Address, value, preventRebuildCache); break; case AddressType::InternalRam: SetMemoryValue(DebugMemoryType::InternalRam, info.Address, value, preventRebuildCache, disableSideEffects); break;
case AddressType::PrgRom: SetMemoryValue(DebugMemoryType::PrgRom, info.Address, value, preventRebuildCache); break; case AddressType::PrgRom: SetMemoryValue(DebugMemoryType::PrgRom, info.Address, value, preventRebuildCache, disableSideEffects); break;
case AddressType::WorkRam: SetMemoryValue(DebugMemoryType::WorkRam, info.Address, value, preventRebuildCache); break; case AddressType::WorkRam: SetMemoryValue(DebugMemoryType::WorkRam, info.Address, value, preventRebuildCache, disableSideEffects); break;
case AddressType::SaveRam: SetMemoryValue(DebugMemoryType::SaveRam, info.Address, value, preventRebuildCache); break; case AddressType::SaveRam: SetMemoryValue(DebugMemoryType::SaveRam, info.Address, value, preventRebuildCache, disableSideEffects); break;
} }
} }
break; break;
case DebugMemoryType::InternalRam: _memoryManager->DebugWrite(address, value); break; case DebugMemoryType::InternalRam: _memoryManager->DebugWrite(address, value, disableSideEffects); break;
case DebugMemoryType::PaletteMemory: _ppu->WritePaletteRAM(address, value); break; case DebugMemoryType::PaletteMemory: _ppu->WritePaletteRAM(address, value); break;
case DebugMemoryType::SpriteMemory: _ppu->GetSpriteRam()[address] = value; break; case DebugMemoryType::SpriteMemory: _ppu->GetSpriteRam()[address] = value; break;
@ -166,7 +166,18 @@ void MemoryDumper::SetMemoryValue(DebugMemoryType memoryType, uint32_t address,
} }
} }
uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address) uint16_t MemoryDumper::GetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects)
{
return GetMemoryValue(memoryType, address, disableSideEffects) | (GetMemoryValue(memoryType, address + 1, disableSideEffects) << 8);
}
void MemoryDumper::SetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, uint16_t value, bool preventRebuildCache, bool disableSideEffects)
{
SetMemoryValue(memoryType, address, (uint8_t)value, preventRebuildCache, disableSideEffects);
SetMemoryValue(memoryType, address + 1, (uint8_t)(value >> 8), preventRebuildCache, disableSideEffects);
}
uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects)
{ {
switch(memoryType) { switch(memoryType) {
case DebugMemoryType::CpuMemory: case DebugMemoryType::CpuMemory:
@ -174,15 +185,15 @@ uint8_t MemoryDumper::GetMemoryValue(DebugMemoryType memoryType, uint32_t addres
_debugger->GetAbsoluteAddressAndType(address, &info); _debugger->GetAbsoluteAddressAndType(address, &info);
if(info.Address >= 0) { if(info.Address >= 0) {
switch(info.Type) { switch(info.Type) {
case AddressType::InternalRam: return GetMemoryValue(DebugMemoryType::InternalRam, info.Address); case AddressType::InternalRam: return GetMemoryValue(DebugMemoryType::InternalRam, info.Address, disableSideEffects);
case AddressType::PrgRom: return GetMemoryValue(DebugMemoryType::PrgRom, info.Address); case AddressType::PrgRom: return GetMemoryValue(DebugMemoryType::PrgRom, info.Address, disableSideEffects);
case AddressType::WorkRam: return GetMemoryValue(DebugMemoryType::WorkRam, info.Address); case AddressType::WorkRam: return GetMemoryValue(DebugMemoryType::WorkRam, info.Address, disableSideEffects);
case AddressType::SaveRam: return GetMemoryValue(DebugMemoryType::SaveRam, info.Address); case AddressType::SaveRam: return GetMemoryValue(DebugMemoryType::SaveRam, info.Address, disableSideEffects);
} }
} }
break; break;
case DebugMemoryType::InternalRam: return _memoryManager->DebugRead(address); case DebugMemoryType::InternalRam: return _memoryManager->DebugRead(address, disableSideEffects);
case DebugMemoryType::PaletteMemory: return _ppu->ReadPaletteRAM(address); case DebugMemoryType::PaletteMemory: return _ppu->ReadPaletteRAM(address);
case DebugMemoryType::SpriteMemory: return _ppu->GetSpriteRam()[address]; case DebugMemoryType::SpriteMemory: return _ppu->GetSpriteRam()[address];

View file

@ -29,8 +29,10 @@ public:
void GetSprites(uint32_t* frameBuffer); void GetSprites(uint32_t* frameBuffer);
void GetPalette(uint32_t* frameBuffer); void GetPalette(uint32_t* frameBuffer);
uint8_t GetMemoryValue(DebugMemoryType memoryType, uint32_t address); uint8_t GetMemoryValue(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects = true);
uint16_t GetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, bool disableSideEffects = true);
void SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache = false, bool disableSideEffects = true);
void SetMemoryValueWord(DebugMemoryType memoryType, uint32_t address, uint16_t value, bool preventRebuildCache = false, bool disableSideEffects = true);
void SetMemoryValues(DebugMemoryType memoryType, uint32_t address, uint8_t* data, int32_t length); void SetMemoryValues(DebugMemoryType memoryType, uint32_t address, uint8_t* data, int32_t length);
void SetMemoryValue(DebugMemoryType memoryType, uint32_t address, uint8_t value, bool preventRebuildCache = false);
void SetMemoryState(DebugMemoryType type, uint8_t *buffer); void SetMemoryState(DebugMemoryType type, uint8_t *buffer);
}; };

View file

@ -101,13 +101,22 @@ uint8_t* MemoryManager::GetInternalRAM()
return _internalRAM; return _internalRAM;
} }
uint8_t MemoryManager::DebugRead(uint16_t addr, bool disableRegisterReads) uint8_t MemoryManager::DebugRead(uint16_t addr, bool disableSideEffects)
{ {
uint8_t value = 0x00; uint8_t value = 0x00;
if(addr <= 0x1FFF) { if(addr <= 0x1FFF) {
value = _internalRAM[addr & 0x07FF]; value = _internalRAM[addr & 0x07FF];
} else if(!disableRegisterReads || addr > 0x4017) { } else {
value = ReadRegister(addr); IMemoryHandler* handler = _ramReadHandlers[addr];
if(handler) {
if(handler == _mapper.get() && disableSideEffects) {
value = ((BaseMapper*)handler)->DebugReadRAM(addr);
} else {
value = handler->ReadRAM(addr);
}
} else {
value = GetOpenBus();
}
} }
CheatManager::ApplyRamCodes(addr, value); CheatManager::ApplyRamCodes(addr, value);
@ -154,12 +163,19 @@ void MemoryManager::Write(uint16_t addr, uint8_t value)
} }
} }
void MemoryManager::DebugWrite(uint16_t addr, uint8_t value) void MemoryManager::DebugWrite(uint16_t addr, uint8_t value, bool disableSideEffects)
{ {
if(addr <= 0x1FFF) { if(addr <= 0x1FFF) {
_internalRAM[addr & 0x07FF] = value; _internalRAM[addr & 0x07FF] = value;
} else { } else {
WriteRegister(addr, value); IMemoryHandler* handler = _ramReadHandlers[addr];
if(handler) {
if(handler == _mapper.get() && disableSideEffects) {
((BaseMapper*)handler)->DebugWriteRAM(addr, value);
} else {
handler->WriteRAM(addr, value);
}
}
} }
} }

View file

@ -39,9 +39,9 @@ class MemoryManager: public Snapshotable
void RegisterIODevice(IMemoryHandler *handler); void RegisterIODevice(IMemoryHandler *handler);
void UnregisterIODevice(IMemoryHandler *handler); void UnregisterIODevice(IMemoryHandler *handler);
uint8_t DebugRead(uint16_t addr, bool disableRegisterReads = true); uint8_t DebugRead(uint16_t addr, bool disableSideEffects = true);
uint16_t DebugReadWord(uint16_t addr); uint16_t DebugReadWord(uint16_t addr);
void DebugWrite(uint16_t addr, uint8_t value); void DebugWrite(uint16_t addr, uint8_t value, bool disableSideEffects = true);
uint8_t* GetInternalRAM(); uint8_t* GetInternalRAM();

View file

@ -85,4 +85,18 @@ public:
break; break;
} }
} }
ApuNoiseState GetState()
{
ApuNoiseState state;
state.Enabled = _enabled;
state.Envelope = ApuEnvelope::GetState();
state.Frequency = (uint32_t)((CPU::GetClockRate(GetNesModel()) / (_period + 1)) / (_modeFlag ? 93 : 1));
state.LengthCounter = ApuLengthCounter::GetState();
state.ModeFlag = _modeFlag;
state.OutputVolume = _lastOutput;
state.Period = _period;
state.ShiftRegister = _shiftRegister;
return state;
}
}; };

View file

@ -90,7 +90,7 @@ bool OggReader::LoadSamples()
void OggReader::ApplySamples(int16_t * buffer, size_t sampleCount, uint8_t volume) void OggReader::ApplySamples(int16_t * buffer, size_t sampleCount, uint8_t volume)
{ {
while(blip_samples_avail(_blipLeft) < sampleCount) { while(blip_samples_avail(_blipLeft) < (int)sampleCount) {
if(!LoadSamples()) { if(!LoadSamples()) {
break; break;
} }

View file

@ -232,4 +232,9 @@ class PPU : public IMemoryHandler, public Snapshotable
uint32_t argbColor = EmulationSettings::GetRgbPalette()[pixelData & 0x3F]; uint32_t argbColor = EmulationSettings::GetRgbPalette()[pixelData & 0x3F];
return (argbColor & 0xFF) + ((argbColor >> 8) & 0xFF) + ((argbColor >> 16) & 0xFF); return (argbColor & 0xFF) + ((argbColor >> 8) & 0xFF) + ((argbColor >> 16) & 0xFF);
} }
static uint16_t GetPixel(uint8_t x, uint8_t y)
{
return PPU::Instance->_currentOutputBuffer[y << 8 | x];
}
}; };

60
Core/ScriptHost.cpp Normal file
View file

@ -0,0 +1,60 @@
#include "stdafx.h"
#include "ScriptHost.h"
#include "ScriptingContext.h"
#include "LuaScriptingContext.h"
ScriptHost::ScriptHost(int scriptId)
{
_scriptId = scriptId;
}
int ScriptHost::GetScriptId()
{
return _scriptId;
}
const char* ScriptHost::GetLog()
{
return _context ? _context->GetLog() : "";
}
bool ScriptHost::LoadScript(string scriptContent, Debugger* debugger)
{
_context.reset();
if(scriptContent.size() > 0) {
_context.reset(new LuaScriptingContext());
if(!_context->LoadScript(scriptContent, debugger)) {
return false;
}
}
return true;
}
void ScriptHost::ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type)
{
if(_context) {
switch(type) {
case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::CpuRead); break;
case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::CpuWrite); break;
case MemoryOperationType::ExecOpCode: _context->CallMemoryCallback(addr, value, CallbackType::CpuExec); break;
}
}
}
void ScriptHost::ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type)
{
if(_context) {
switch(type) {
case MemoryOperationType::Read: _context->CallMemoryCallback(addr, value, CallbackType::PpuRead); break;
case MemoryOperationType::Write: _context->CallMemoryCallback(addr, value, CallbackType::PpuWrite); break;
}
}
}
void ScriptHost::ProcessEvent(EventType eventType)
{
if(_context) {
_context->CallEventCallback(eventType);
}
}

25
Core/ScriptHost.h Normal file
View file

@ -0,0 +1,25 @@
#pragma once
#include "stdafx.h"
#include "DebuggerTypes.h"
class ScriptingContext;
class Debugger;
class ScriptHost
{
private:
shared_ptr<ScriptingContext> _context;
int _scriptId;
public:
ScriptHost(int scriptId);
int GetScriptId();
const char* GetLog();
bool LoadScript(string scriptContent, Debugger* debugger);
void ProcessCpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type);
void ProcessPpuOperation(uint16_t addr, uint8_t value, MemoryOperationType type);
void ProcessEvent(EventType eventType);
};

87
Core/ScriptingContext.cpp Normal file
View file

@ -0,0 +1,87 @@
#include "stdafx.h"
#include "ScriptingContext.h"
#include "DebuggerTypes.h"
void ScriptingContext::Log(string message)
{
auto lock = _logLock.AcquireSafe();
_logRows.push_back(message);
if(_logRows.size() > 500) {
_logRows.pop_front();
}
}
const char* ScriptingContext::GetLog()
{
auto lock = _logLock.AcquireSafe();
stringstream ss;
for(string &msg : _logRows) {
ss << msg << "\n";
}
_log = ss.str();
return _log.c_str();
}
int ScriptingContext::CallEventCallback(EventType type)
{
_inStartFrameEvent = type == EventType::StartFrame;
int returnValue = InternalCallEventCallback(type);
_inStartFrameEvent = false;
return returnValue;
}
bool ScriptingContext::CheckInStartFrameEvent()
{
return _inStartFrameEvent;
}
void ScriptingContext::RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference)
{
if(endAddr < startAddr) {
return;
}
if(startAddr == 0 && endAddr == 0) {
if(type <= CallbackType::CpuExec) {
endAddr = 0xFFFF;
} else {
endAddr = 0x3FFF;
}
}
for(int i = startAddr; i < endAddr; i++) {
_callbacks[(int)type][i].push_back(reference);
}
}
void ScriptingContext::UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference)
{
if(endAddr < startAddr) {
return;
}
if(startAddr == 0 && endAddr == 0) {
if(type <= CallbackType::CpuExec) {
endAddr = 0xFFFF;
} else {
endAddr = 0x3FFF;
}
}
for(int i = startAddr; startAddr < endAddr; i++) {
vector<int> &refs = _callbacks[(int)type][i];
refs.erase(std::remove(refs.begin(), refs.end(), reference), refs.end());
}
}
void ScriptingContext::RegisterEventCallback(EventType type, int reference)
{
_eventCallbacks[(int)type].push_back(reference);
}
void ScriptingContext::UnregisterEventCallback(EventType type, int reference)
{
vector<int> &callbacks = _eventCallbacks[(int)type];
callbacks.erase(std::remove(callbacks.begin(), callbacks.end(), reference), callbacks.end());
}

46
Core/ScriptingContext.h Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "stdafx.h"
#include <deque>
#include "../Utilities/SimpleLock.h"
#include "DebuggerTypes.h"
class Debugger;
enum class CallbackType
{
CpuRead = 0,
CpuWrite = 1,
CpuExec = 2,
PpuRead = 3,
PpuWrite = 4
};
class ScriptingContext
{
private:
std::deque<string> _logRows;
string _log;
SimpleLock _logLock;
bool _inStartFrameEvent = false;
protected:
vector<int> _callbacks[5][0x10000];
vector<int> _eventCallbacks[7];
public:
virtual bool LoadScript(string scriptContent, Debugger* debugger) = 0;
void Log(string message);
const char* GetLog();
virtual void CallMemoryCallback(int addr, int value, CallbackType type) = 0;
virtual int InternalCallEventCallback(EventType type) = 0;
int CallEventCallback(EventType type);
bool CheckInStartFrameEvent();
void RegisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference);
void UnregisterMemoryCallback(CallbackType type, int startAddr, int endAddr, int reference);
void RegisterEventCallback(EventType type, int reference);
void UnregisterEventCallback(EventType type, int reference);
};

View file

@ -183,4 +183,22 @@ public:
_reloadSweep = false; _reloadSweep = false;
} }
} }
ApuSquareState GetState()
{
ApuSquareState state;
state.Duty = _duty;
state.DutyPosition = _dutyPos;
state.Enabled = _enabled;
state.Envelope = ApuEnvelope::GetState();
state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 16.0 / (_realPeriod + 1));
state.LengthCounter = ApuLengthCounter::GetState();
state.OutputVolume = _lastOutput;
state.Period = _realPeriod;
state.SweepEnabled = _sweepEnabled;
state.SweepNegate = _sweepNegate;
state.SweepPeriod = _sweepPeriod;
state.SweepShift = _sweepShift;
return state;
}
}; };

View file

@ -32,7 +32,7 @@ bool StandardController::IsMicrophoneActive()
return false; return false;
} }
uint8_t StandardController::GetButtonState() ButtonState StandardController::GetButtonState()
{ {
ButtonState state; ButtonState state;
@ -70,12 +70,12 @@ uint8_t StandardController::GetButtonState()
} }
} }
return state.ToByte(); return state;
} }
uint32_t StandardController::GetNetPlayState() uint32_t StandardController::GetNetPlayState()
{ {
return GetButtonState(); return GetButtonState().ToByte();
} }
uint8_t StandardController::GetPortOutput() uint8_t StandardController::GetPortOutput()
@ -132,7 +132,7 @@ void StandardController::RefreshStateBuffer()
uint8_t StandardController::RefreshState() uint8_t StandardController::RefreshState()
{ {
return GetButtonState(); return GetButtonState().ToByte();
} }
void StandardController::AddAdditionalController(shared_ptr<BaseControlDevice> controller) void StandardController::AddAdditionalController(shared_ptr<BaseControlDevice> controller)

View file

@ -12,7 +12,6 @@ private:
uint8_t _lastButtonState = 0; uint8_t _lastButtonState = 0;
shared_ptr<BaseControlDevice> _additionalController; shared_ptr<BaseControlDevice> _additionalController;
uint8_t GetButtonState();
protected: protected:
uint8_t RefreshState() override; uint8_t RefreshState() override;
@ -21,6 +20,7 @@ protected:
public: public:
StandardController(uint8_t port, bool emptyPort = false); StandardController(uint8_t port, bool emptyPort = false);
ButtonState GetButtonState();
uint32_t GetNetPlayState() override; uint32_t GetNetPlayState() override;
uint8_t GetPortOutput() override; uint8_t GetPortOutput() override;

View file

@ -22,7 +22,6 @@ protected:
//The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero. //The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero.
if(_lengthCounter > 0 && _linearCounter > 0) { if(_lengthCounter > 0 && _linearCounter > 0) {
_sequencePosition = (_sequencePosition + 1) & 0x1F; _sequencePosition = (_sequencePosition + 1) & 0x1F;
if(_period >= 2 || !EmulationSettings::CheckFlag(EmulationFlags::SilenceTriangleHighFreq)) { if(_period >= 2 || !EmulationSettings::CheckFlag(EmulationFlags::SilenceTriangleHighFreq)) {
//Disabling the triangle channel when period is < 2 removes "pops" in the audio that are caused by the ultrasonic frequencies //Disabling the triangle channel when period is < 2 removes "pops" in the audio that are caused by the ultrasonic frequencies
@ -101,4 +100,16 @@ public:
_linearReloadFlag = false; _linearReloadFlag = false;
} }
} }
ApuTriangleState GetState()
{
ApuTriangleState state;
state.Enabled = _enabled;
state.Frequency = (uint32_t)(CPU::GetClockRate(GetNesModel()) / 32.0 / (_period + 1));
state.LengthCounter = ApuLengthCounter::GetState();
state.OutputVolume = _lastOutput;
state.Period = _period;
state.SequencePosition = _sequencePosition;
return state;
}
}; };

View file

@ -78,7 +78,6 @@ struct CartridgeState
uint32_t Nametables[8]; uint32_t Nametables[8];
}; };
struct PPUControlFlags struct PPUControlFlags
{ {
bool VerticalWrite; bool VerticalWrite;
@ -137,4 +136,97 @@ struct SpriteInfo : TileInfo
uint8_t SpriteX; uint8_t SpriteX;
bool VerticalMirror; //used by HD ppu bool VerticalMirror; //used by HD ppu
};
struct ApuLengthCounterState
{
bool Halt;
uint8_t Counter;
uint8_t ReloadValue;
};
struct ApuEnvelopeState
{
bool StartFlag;
bool Loop;
bool ConstantVolume;
uint8_t Divider;
uint8_t Counter;
uint8_t Volume;
};
struct ApuSquareState
{
uint8_t Duty;
uint8_t DutyPosition;
uint16_t Period;
bool SweepEnabled;
bool SweepNegate;
uint8_t SweepPeriod;
uint8_t SweepShift;
bool Enabled;
uint8_t OutputVolume;
uint32_t Frequency;
ApuLengthCounterState LengthCounter;
ApuEnvelopeState Envelope;
};
struct ApuTriangleState
{
uint16_t Period;
uint8_t SequencePosition;
bool Enabled;
uint32_t Frequency;
uint8_t OutputVolume;
ApuLengthCounterState LengthCounter;
};
struct ApuNoiseState
{
uint16_t Period;
uint16_t ShiftRegister;
bool ModeFlag;
bool Enabled;
uint32_t Frequency;
uint8_t OutputVolume;
ApuLengthCounterState LengthCounter;
ApuEnvelopeState Envelope;
};
struct ApuDmcState
{
uint16_t SampleAddr;
uint16_t SampleLength;
bool Loop;
bool IrqEnabled;
uint16_t Period;
uint16_t BytesRemaining;
uint32_t Frequency;
uint8_t OutputVolume;
};
struct ApuFrameCounterState
{
bool FiveStepMode;
uint8_t SequencePosition;
bool IrqEnabled;
};
struct ApuState
{
ApuSquareState Square1;
ApuSquareState Square2;
ApuTriangleState Triangle;
ApuNoiseState Noise;
ApuDmcState Dmc;
ApuFrameCounterState FrameCounter;
}; };

View file

@ -90,6 +90,8 @@ namespace Mesen.GUI.Config
public class DebugInfo public class DebugInfo
{ {
private const int MaxRecentScripts = 10;
public DebugViewInfo LeftView; public DebugViewInfo LeftView;
public DebugViewInfo RightView; public DebugViewInfo RightView;
@ -161,6 +163,13 @@ namespace Mesen.GUI.Config
public bool TraceIndentCode = false; public bool TraceIndentCode = false;
public Size TraceLoggerSize = new Size(0, 0); public Size TraceLoggerSize = new Size(0, 0);
public Size ScriptWindowSize = new Size(0, 0);
public int ScriptCodeWindowHeight = 0;
public List<string> RecentScripts = new List<string>();
public bool SaveScriptBeforeRun = true;
public bool AutoReloadScript = false;
public int ScriptZoom = 100;
public DebugInfo() public DebugInfo()
{ {
LeftView = new DebugViewInfo(); LeftView = new DebugViewInfo();
@ -178,5 +187,19 @@ namespace Mesen.GUI.Config
StatusFormat = StatusFlagFormat.Hexadecimal StatusFormat = StatusFlagFormat.Hexadecimal
}; };
} }
public void AddRecentScript(string scriptFile)
{
string existingItem = RecentScripts.Where((file) => file == scriptFile).FirstOrDefault();
if(existingItem != null) {
RecentScripts.Remove(existingItem);
}
RecentScripts.Insert(0, scriptFile);
if(RecentScripts.Count > DebugInfo.MaxRecentScripts) {
RecentScripts.RemoveAt(DebugInfo.MaxRecentScripts);
}
ConfigManager.ApplyChanges();
}
} }
} }

View file

@ -84,7 +84,7 @@ namespace Mesen.GUI.Controls
} else if(this.FixedPanel == FixedPanel.Panel2) { } else if(this.FixedPanel == FixedPanel.Panel2) {
_originalDistance = this.SplitterDistance; _originalDistance = this.SplitterDistance;
_originalMinSize = this.Panel2MinSize; _originalMinSize = this.Panel2MinSize;
this.Panel2MinSize = 4; this.Panel2MinSize = 2;
this.SplitterDistance = this.Orientation == Orientation.Horizontal ? this.Height : this.Width; this.SplitterDistance = this.Orientation == Orientation.Horizontal ? this.Height : this.Width;
this.PanelCollapsed?.Invoke(this, EventArgs.Empty); this.PanelCollapsed?.Invoke(this, EventArgs.Empty);

View file

@ -25,6 +25,7 @@ namespace Mesen.GUI.Debugger
case DebugWindow.MemoryViewer: frm = new frmMemoryViewer(); break; case DebugWindow.MemoryViewer: frm = new frmMemoryViewer(); break;
case DebugWindow.Assembler: frm = new frmAssembler(); break; case DebugWindow.Assembler: frm = new frmAssembler(); break;
case DebugWindow.Debugger: frm = new frmDebugger(); break; case DebugWindow.Debugger: frm = new frmDebugger(); break;
case DebugWindow.ScriptWindow: frm = new frmScript(); break;
} }
_openedWindows.Add(frm); _openedWindows.Add(frm);
frm.FormClosed += Debugger_FormClosed; frm.FormClosed += Debugger_FormClosed;
@ -97,6 +98,7 @@ namespace Mesen.GUI.Debugger
MemoryViewer, MemoryViewer,
TraceLogger, TraceLogger,
Assembler, Assembler,
Debugger Debugger,
ScriptWindow,
} }
} }

View file

@ -0,0 +1,266 @@
using System;
using System.Drawing;
using System.Drawing.Printing;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Item of autocomplete menu
/// </summary>
public class AutocompleteItem
{
public string Text;
public int ImageIndex = -1;
public object Tag;
string toolTipTitle;
string toolTipText;
string menuText;
public AutocompleteMenu Parent { get; internal set; }
public AutocompleteItem()
{
}
public AutocompleteItem(string text)
{
Text = text;
}
public AutocompleteItem(string text, int imageIndex)
: this(text)
{
this.ImageIndex = imageIndex;
}
public AutocompleteItem(string text, int imageIndex, string menuText)
: this(text, imageIndex)
{
this.menuText = menuText;
}
public AutocompleteItem(string text, int imageIndex, string menuText, string toolTipTitle, string toolTipText)
: this(text, imageIndex, menuText)
{
this.toolTipTitle = toolTipTitle;
this.toolTipText = toolTipText;
}
/// <summary>
/// Returns text for inserting into Textbox
/// </summary>
public virtual string GetTextForReplace()
{
return Text;
}
/// <summary>
/// Compares fragment text with this item
/// </summary>
public virtual CompareResult Compare(string fragmentText)
{
if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) &&
Text != fragmentText)
return CompareResult.VisibleAndSelected;
return CompareResult.Hidden;
}
/// <summary>
/// Returns text for display into popup menu
/// </summary>
public override string ToString()
{
return menuText ?? Text;
}
/// <summary>
/// This method is called after item inserted into text
/// </summary>
public virtual void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e)
{
;
}
/// <summary>
/// Title for tooltip.
/// </summary>
/// <remarks>Return null for disable tooltip for this item</remarks>
public virtual string ToolTipTitle
{
get { return toolTipTitle; }
set { toolTipTitle = value; }
}
/// <summary>
/// Tooltip text.
/// </summary>
/// <remarks>For display tooltip text, ToolTipTitle must be not null</remarks>
public virtual string ToolTipText
{
get{ return toolTipText; }
set { toolTipText = value; }
}
/// <summary>
/// Menu text. This text is displayed in the drop-down menu.
/// </summary>
public virtual string MenuText
{
get { return menuText; }
set { menuText = value; }
}
/// <summary>
/// Fore color of text of item
/// </summary>
public virtual Color ForeColor
{
get { return Color.Transparent; }
set { throw new NotImplementedException("Override this property to change color"); }
}
/// <summary>
/// Back color of item
/// </summary>
public virtual Color BackColor
{
get { return Color.Transparent; }
set { throw new NotImplementedException("Override this property to change color"); }
}
}
public enum CompareResult
{
/// <summary>
/// Item do not appears
/// </summary>
Hidden,
/// <summary>
/// Item appears
/// </summary>
Visible,
/// <summary>
/// Item appears and will selected
/// </summary>
VisibleAndSelected
}
/// <summary>
/// Autocomplete item for code snippets
/// </summary>
/// <remarks>Snippet can contain special char ^ for caret position.</remarks>
public class SnippetAutocompleteItem : AutocompleteItem
{
public SnippetAutocompleteItem(string snippet)
{
Text = snippet.Replace("\r", "");
ToolTipTitle = "Code snippet:";
ToolTipText = Text;
}
public override string ToString()
{
return MenuText ?? Text.Replace("\n", " ").Replace("^", "");
}
public override string GetTextForReplace()
{
return Text;
}
public override void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e)
{
e.Tb.BeginUpdate();
e.Tb.Selection.BeginUpdate();
//remember places
var p1 = popupMenu.Fragment.Start;
var p2 = e.Tb.Selection.Start;
//do auto indent
if (e.Tb.AutoIndent)
{
for (int iLine = p1.iLine + 1; iLine <= p2.iLine; iLine++)
{
e.Tb.Selection.Start = new Place(0, iLine);
e.Tb.DoAutoIndent(iLine);
}
}
e.Tb.Selection.Start = p1;
//move caret position right and find char ^
while (e.Tb.Selection.CharBeforeStart != '^')
if (!e.Tb.Selection.GoRightThroughFolded())
break;
//remove char ^
e.Tb.Selection.GoLeft(true);
e.Tb.InsertText("");
//
e.Tb.Selection.EndUpdate();
e.Tb.EndUpdate();
}
/// <summary>
/// Compares fragment text with this item
/// </summary>
public override CompareResult Compare(string fragmentText)
{
if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) &&
Text != fragmentText)
return CompareResult.Visible;
return CompareResult.Hidden;
}
}
/// <summary>
/// This autocomplete item appears after dot
/// </summary>
public class MethodAutocompleteItem : AutocompleteItem
{
string firstPart;
string lowercaseText;
public MethodAutocompleteItem(string text)
: base(text)
{
lowercaseText = Text.ToLower();
}
public override CompareResult Compare(string fragmentText)
{
int i = fragmentText.LastIndexOf('.');
if (i < 0)
return CompareResult.Hidden;
string lastPart = fragmentText.Substring(i + 1);
firstPart = fragmentText.Substring(0, i);
if(lastPart=="") return CompareResult.Visible;
if(Text.StartsWith(lastPart, StringComparison.InvariantCultureIgnoreCase))
return CompareResult.VisibleAndSelected;
if(lowercaseText.Contains(lastPart.ToLower()))
return CompareResult.Visible;
return CompareResult.Hidden;
}
public override string GetTextForReplace()
{
return firstPart + "." + Text;
}
}
/// <summary>
/// This Item does not check correspondence to current text fragment.
/// SuggestItem is intended for dynamic menus.
/// </summary>
public class SuggestItem : AutocompleteItem
{
public SuggestItem(string text, int imageIndex):base(text, imageIndex)
{
}
public override CompareResult Compare(string fragmentText)
{
return CompareResult.Visible;
}
}
}

View file

@ -0,0 +1,750 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Text.RegularExpressions;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Popup menu for autocomplete
/// </summary>
[Browsable(false)]
public class AutocompleteMenu : UserControl
{
AutocompleteListView listView;
public Range Fragment { get; internal set; }
/// <summary>
/// Regex pattern for serach fragment around caret
/// </summary>
public string SearchPattern { get; set; }
/// <summary>
/// Minimum fragment length for popup
/// </summary>
public int MinFragmentLength { get; set; }
/// <summary>
/// User selects item
/// </summary>
public event EventHandler<SelectingEventArgs> Selecting;
/// <summary>
/// It fires after item inserting
/// </summary>
public event EventHandler<SelectedEventArgs> Selected;
/// <summary>
/// Occurs when popup menu is opening
/// </summary>
public event EventHandler<CancelEventArgs> Opening;
/// <summary>
/// Allow TAB for select menu item
/// </summary>
public bool AllowTabKey { get { return listView.AllowTabKey; } set { listView.AllowTabKey = value; } }
/// <summary>
/// Interval of menu appear (ms)
/// </summary>
public int AppearInterval { get { return listView.AppearInterval; } set { listView.AppearInterval = value; } }
/// <summary>
/// Back color of selected item
/// </summary>
[DefaultValue(typeof(Color), "Orange")]
public Color SelectedColor
{
get { return listView.SelectedColor; }
set { listView.SelectedColor = value; }
}
/// <summary>
/// Border color of hovered item
/// </summary>
[DefaultValue(typeof(Color), "Red")]
public Color HoveredColor
{
get { return listView.HoveredColor; }
set { listView.HoveredColor = value; }
}
public AutocompleteMenu(FastColoredTextBox tb, Form parentForm)
{
// create a new popup and add the list view to it
Visible = false;
BorderStyle = BorderStyle.FixedSingle;
AutoSize = false;
Margin = Padding.Empty;
Padding = Padding.Empty;
BackColor = Color.White;
listView = new AutocompleteListView(this, tb, parentForm);
CalcSize();
this.Controls.Add(listView);
SearchPattern = @"[\w\.]";
MinFragmentLength = 2;
}
public new Font Font
{
get { return listView.Font; }
set { listView.Font = value; }
}
internal void OnOpening(CancelEventArgs args)
{
if (Opening != null)
Opening(this, args);
}
public void Close()
{
listView.toolTip.Hide(listView);
this.Hide();
}
internal void CalcSize()
{
Size = new System.Drawing.Size(listView.Size.Width + 2, listView.Size.Height + 2);
}
public virtual void OnSelecting()
{
listView.OnSelecting();
}
public void SelectNext(int shift)
{
listView.SelectNext(shift);
}
internal void OnSelecting(SelectingEventArgs args)
{
if (Selecting != null)
Selecting(this, args);
}
public void OnSelected(SelectedEventArgs args)
{
if (Selected != null)
Selected(this, args);
}
public AutocompleteListView Items
{
get { return listView; }
}
/// <summary>
/// Shows popup menu immediately
/// </summary>
/// <param name="forced">If True - MinFragmentLength will be ignored</param>
public void Show(bool forced)
{
Items.DoAutocomplete(forced);
}
/// <summary>
/// Minimal size of menu
/// </summary>
public new Size MinimumSize
{
get { return Items.MinimumSize; }
set { Items.MinimumSize = value; }
}
/// <summary>
/// Image list of menu
/// </summary>
public ImageList ImageList
{
get { return Items.ImageList; }
set { Items.ImageList = value; }
}
/// <summary>
/// Tooltip duration (ms)
/// </summary>
public int ToolTipDuration
{
get { return Items.ToolTipDuration; }
set { Items.ToolTipDuration = value; }
}
/// <summary>
/// Tooltip
/// </summary>
public ToolTip ToolTip
{
get { return Items.toolTip; }
set { Items.toolTip = value; }
}
}
[System.ComponentModel.ToolboxItem(false)]
public class AutocompleteListView : UserControl
{
public event EventHandler FocussedItemIndexChanged;
internal List<AutocompleteItem> visibleItems;
IEnumerable<AutocompleteItem> sourceItems = new List<AutocompleteItem>();
int focussedItemIndex = 0;
int hoveredItemIndex = -1;
private int ItemHeight
{
get { return Font.Height + 2; }
}
private AutocompleteMenu Menu { get; set; }
int oldItemCount = 0;
FastColoredTextBox tb;
internal ToolTip toolTip = new ToolTip();
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
internal bool AllowTabKey { get; set; }
public ImageList ImageList { get; set; }
internal int AppearInterval { get { return timer.Interval; } set { timer.Interval = value; } }
internal int ToolTipDuration { get; set; }
public override Size GetPreferredSize(Size proposedSize)
{
return Size;
}
public Color SelectedColor { get; set; }
public Color HoveredColor { get; set; }
public int FocussedItemIndex
{
get { return focussedItemIndex; }
set
{
if (focussedItemIndex != value)
{
focussedItemIndex = value;
if (FocussedItemIndexChanged != null)
FocussedItemIndexChanged(this, EventArgs.Empty);
}
}
}
public AutocompleteItem FocussedItem
{
get
{
if (FocussedItemIndex >= 0 && focussedItemIndex < visibleItems.Count)
return visibleItems[focussedItemIndex];
return null;
}
set
{
FocussedItemIndex = visibleItems.IndexOf(value);
}
}
public Form AutocompleteParent { get; set; }
internal AutocompleteListView(AutocompleteMenu menu, FastColoredTextBox tb, Form parent)
{
Menu = menu;
AutocompleteParent = parent;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
base.Font = new Font(FontFamily.GenericSansSerif, 9);
visibleItems = new List<AutocompleteItem>();
VerticalScroll.SmallChange = ItemHeight;
MaximumSize = new Size(Size.Width, 180);
toolTip.ShowAlways = false;
AppearInterval = 200;
timer.Tick += new EventHandler(timer_Tick);
SelectedColor = Color.Orange;
HoveredColor = Color.Red;
ToolTipDuration = 30000;
this.tb = tb;
tb.KeyDown += new KeyEventHandler(tb_KeyDown);
tb.SelectionChanged += new EventHandler(tb_SelectionChanged);
tb.KeyPressed += new KeyPressEventHandler(tb_KeyPressed);
Form form = tb.FindForm();
if (form != null)
{
form.LocationChanged += (o, e) => Menu.Close();
form.ResizeBegin += (o, e) => Menu.Close();
form.FormClosing += (o, e) => Menu.Close();
form.LostFocus += (o, e) => Menu.Close();
}
tb.LostFocus += (o, e) =>
{
if (!Menu.Focused) Menu.Close();
};
tb.Scroll += (o, e) => Menu.Close();
this.VisibleChanged += (o, e) =>
{
if (this.Visible)
DoSelectedVisible();
};
}
void tb_KeyPressed(object sender, KeyPressEventArgs e)
{
bool backspaceORdel = e.KeyChar == '\b' || e.KeyChar == 0xff;
/*
if (backspaceORdel)
prevSelection = tb.Selection.Start;*/
if (Menu.Visible && !backspaceORdel)
DoAutocomplete(false);
else
ResetTimer(timer);
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
DoAutocomplete(false);
}
void ResetTimer(System.Windows.Forms.Timer timer)
{
timer.Stop();
timer.Start();
}
internal void DoAutocomplete()
{
DoAutocomplete(false);
}
internal void DoAutocomplete(bool forced)
{
if (!Menu.Enabled)
{
Menu.Close();
return;
}
visibleItems.Clear();
FocussedItemIndex = 0;
VerticalScroll.Value = 0;
//some magic for update scrolls
AutoScrollMinSize += new Size(1, 0);
AutoScrollMinSize -= new Size(1, 0);
//get fragment around caret
Range fragment = tb.Selection.GetFragment(Menu.SearchPattern);
string text = fragment.Text;
//calc screen point for popup menu
Point point = tb.PlaceToPoint(fragment.End);
Point offset = tb.PointToScreen(point);
point = AutocompleteParent.PointToClient(offset);
point.Offset(2, tb.CharHeight);
//
if (forced || (text.Length >= Menu.MinFragmentLength
&& tb.Selection.IsEmpty /*pops up only if selected range is empty*/
&& (tb.Selection.Start > fragment.Start || text.Length == 0/*pops up only if caret is after first letter*/)))
{
Menu.Fragment = fragment;
bool foundSelected = false;
//build popup menu
foreach (var item in sourceItems)
{
item.Parent = Menu;
CompareResult res = item.Compare(text);
if(res != CompareResult.Hidden)
visibleItems.Add(item);
if (res == CompareResult.VisibleAndSelected && !foundSelected)
{
foundSelected = true;
FocussedItemIndex = visibleItems.Count - 1;
}
}
if (foundSelected)
{
AdjustScroll();
DoSelectedVisible();
}
}
//show popup menu
if (Count > 0)
{
if (!Menu.Visible)
{
CancelEventArgs args = new CancelEventArgs();
Menu.OnOpening(args);
if (!args.Cancel)
{
Menu.Location = point;
Menu.Parent = AutocompleteParent;
Menu.Show();
Menu.BringToFront();
}
}
else
Invalidate();
}
else
Menu.Close();
}
void tb_SelectionChanged(object sender, EventArgs e)
{
/*
FastColoredTextBox tb = sender as FastColoredTextBox;
if (Math.Abs(prevSelection.iChar - tb.Selection.Start.iChar) > 1 ||
prevSelection.iLine != tb.Selection.Start.iLine)
Menu.Close();
prevSelection = tb.Selection.Start;*/
if (Menu.Visible)
{
bool needClose = false;
if (!tb.Selection.IsEmpty)
needClose = true;
else
if (!Menu.Fragment.Contains(tb.Selection.Start))
{
if (tb.Selection.Start.iLine == Menu.Fragment.End.iLine && tb.Selection.Start.iChar == Menu.Fragment.End.iChar + 1)
{
//user press key at end of fragment
char c = tb.Selection.CharBeforeStart;
if (!Regex.IsMatch(c.ToString(), Menu.SearchPattern))//check char
needClose = true;
}
else
needClose = true;
}
if (needClose)
Menu.Close();
}
}
void tb_KeyDown(object sender, KeyEventArgs e)
{
var tb = sender as FastColoredTextBox;
if (Menu.Visible)
if (ProcessKey(e.KeyCode, e.Modifiers))
e.Handled = true;
if (!Menu.Visible)
{
if (tb.HotkeysMapping.ContainsKey(e.KeyData) && tb.HotkeysMapping[e.KeyData] == FCTBAction.AutocompleteMenu)
{
DoAutocomplete();
e.Handled = true;
}
else
{
if (e.KeyCode == Keys.Escape && timer.Enabled)
timer.Stop();
}
}
}
void AdjustScroll()
{
Range fragment = tb.Selection.GetFragment(Menu.SearchPattern);
string text = fragment.Text;
//calc screen point for popup menu
Point point = tb.PlaceToPoint(fragment.End);
Point offset = tb.PointToScreen(point);
point = AutocompleteParent.PointToClient(offset);
point.Offset(2, tb.CharHeight);
if(Menu.Width + point.X > AutocompleteParent.ClientSize.Width - 10) {
point.X -= Menu.Width;
}
if(Menu.Height + point.Y > AutocompleteParent.ClientSize.Height - 10) {
point.Y -= Menu.Height + 15;
}
Menu.Location = point;
if (oldItemCount == visibleItems.Count)
return;
int needHeight = ItemHeight * visibleItems.Count + 1;
Height = Math.Min(needHeight, MaximumSize.Height);
Menu.CalcSize();
AutoScrollMinSize = new Size(0, needHeight);
oldItemCount = visibleItems.Count;
}
protected override void OnPaint(PaintEventArgs e)
{
AdjustScroll();
var itemHeight = ItemHeight;
int startI = VerticalScroll.Value / itemHeight - 1;
int finishI = (VerticalScroll.Value + ClientSize.Height) / itemHeight + 1;
startI = Math.Max(startI, 0);
finishI = Math.Min(finishI, visibleItems.Count);
int y = 0;
int leftPadding = 18;
for (int i = startI; i < finishI; i++)
{
y = i * itemHeight - VerticalScroll.Value;
var item = visibleItems[i];
if(item.BackColor != Color.Transparent)
using (var brush = new SolidBrush(item.BackColor))
e.Graphics.FillRectangle(brush, 1, y, ClientSize.Width, itemHeight);
if (ImageList != null && visibleItems[i].ImageIndex >= 0)
e.Graphics.DrawImage(ImageList.Images[item.ImageIndex], 1, y);
if (i == FocussedItemIndex)
using (var selectedBrush = new LinearGradientBrush(new Point(0, y - 3), new Point(0, y + itemHeight), Color.Transparent, SelectedColor))
using (var pen = new Pen(SelectedColor))
{
e.Graphics.FillRectangle(selectedBrush, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight);
e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight);
}
if (i == hoveredItemIndex)
using(var pen = new Pen(HoveredColor))
e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - leftPadding, itemHeight);
using (var brush = new SolidBrush(item.ForeColor != Color.Transparent ? item.ForeColor : ForeColor))
e.Graphics.DrawString(item.ToString(), Font, brush, leftPadding, y);
}
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
FocussedItemIndex = PointToItemIndex(e.Location);
DoSelectedVisible();
Invalidate();
}
}
protected override void OnMouseDoubleClick(MouseEventArgs e)
{
base.OnMouseDoubleClick(e);
FocussedItemIndex = PointToItemIndex(e.Location);
Invalidate();
OnSelecting();
}
internal virtual void OnSelecting()
{
if (FocussedItemIndex < 0 || FocussedItemIndex >= visibleItems.Count)
return;
tb.TextSource.Manager.BeginAutoUndoCommands();
try
{
AutocompleteItem item = FocussedItem;
SelectingEventArgs args = new SelectingEventArgs()
{
Item = item,
SelectedIndex = FocussedItemIndex
};
Menu.OnSelecting(args);
if (args.Cancel)
{
FocussedItemIndex = args.SelectedIndex;
Invalidate();
return;
}
if (!args.Handled)
{
var fragment = Menu.Fragment;
DoAutocomplete(item, fragment);
}
Menu.Close();
//
SelectedEventArgs args2 = new SelectedEventArgs()
{
Item = item,
Tb = Menu.Fragment.tb
};
item.OnSelected(Menu, args2);
Menu.OnSelected(args2);
}
finally
{
tb.TextSource.Manager.EndAutoUndoCommands();
}
}
private void DoAutocomplete(AutocompleteItem item, Range fragment)
{
string newText = item.GetTextForReplace();
//replace text of fragment
var tb = fragment.tb;
tb.BeginAutoUndo();
tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource));
if (tb.Selection.ColumnSelectionMode)
{
var start = tb.Selection.Start;
var end = tb.Selection.End;
start.iChar = fragment.Start.iChar;
end.iChar = fragment.End.iChar;
tb.Selection.Start = start;
tb.Selection.End = end;
}
else
{
tb.Selection.Start = fragment.Start;
tb.Selection.End = fragment.End;
}
tb.InsertText(newText);
tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource));
tb.EndAutoUndo();
tb.Focus();
}
int PointToItemIndex(Point p)
{
return (p.Y + VerticalScroll.Value) / ItemHeight;
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
ProcessKey(keyData, Keys.None);
return base.ProcessCmdKey(ref msg, keyData);
}
private bool ProcessKey(Keys keyData, Keys keyModifiers)
{
if (keyModifiers == Keys.None)
switch (keyData)
{
case Keys.Down:
SelectNext(+1);
return true;
case Keys.PageDown:
SelectNext(+10);
return true;
case Keys.Up:
SelectNext(-1);
return true;
case Keys.PageUp:
SelectNext(-10);
return true;
case Keys.Enter:
OnSelecting();
return true;
case Keys.Tab:
if (!AllowTabKey)
break;
OnSelecting();
return true;
case Keys.Escape:
Menu.Close();
return true;
}
return false;
}
public void SelectNext(int shift)
{
FocussedItemIndex = Math.Max(0, Math.Min(FocussedItemIndex + shift, visibleItems.Count - 1));
DoSelectedVisible();
//
Invalidate();
}
private void DoSelectedVisible()
{
if (FocussedItem != null)
SetToolTip(FocussedItem);
var y = FocussedItemIndex * ItemHeight - VerticalScroll.Value;
if (y < 0)
VerticalScroll.Value = FocussedItemIndex * ItemHeight;
if (y > ClientSize.Height - ItemHeight)
VerticalScroll.Value = Math.Min(VerticalScroll.Maximum, FocussedItemIndex * ItemHeight - ClientSize.Height + ItemHeight);
//some magic for update scrolls
AutoScrollMinSize -= new Size(1, 0);
AutoScrollMinSize += new Size(1, 0);
}
private void SetToolTip(AutocompleteItem autocompleteItem)
{
var title = autocompleteItem.ToolTipTitle;
var text = autocompleteItem.ToolTipText;
Control window = tb;
if (string.IsNullOrEmpty(title))
{
toolTip.Hide(window);
return;
}
var location = new Point(Right + 3 + Menu.Left, Menu.Top);
if (string.IsNullOrEmpty(text))
{
toolTip.ToolTipTitle = null;
if (ToolTipDuration == 0)
toolTip.Show(title, window, location);
else
toolTip.Show(title, window, location, ToolTipDuration);
}
else
{
toolTip.ToolTipTitle = title;
if (ToolTipDuration == 0)
toolTip.Show(text, window, location);
else
toolTip.Show(text, window, location, ToolTipDuration);
}
}
public int Count
{
get { return visibleItems.Count; }
}
public void SetAutocompleteItems(ICollection<string> items)
{
List<AutocompleteItem> list = new List<AutocompleteItem>(items.Count);
foreach (var item in items)
list.Add(new AutocompleteItem(item));
SetAutocompleteItems(list);
}
public void SetAutocompleteItems(IEnumerable<AutocompleteItem> items)
{
sourceItems = items;
}
}
public class SelectingEventArgs : EventArgs
{
public AutocompleteItem Item { get; internal set; }
public bool Cancel {get;set;}
public int SelectedIndex{get;set;}
public bool Handled { get; set; }
}
public class SelectedEventArgs : EventArgs
{
public AutocompleteItem Item { get; internal set; }
public FastColoredTextBox Tb { get; set; }
}
}

View file

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Base class for bookmark collection
/// </summary>
public abstract class BaseBookmarks : ICollection<Bookmark>, IDisposable
{
#region ICollection
public abstract void Add(Bookmark item);
public abstract void Clear();
public abstract bool Contains(Bookmark item);
public abstract void CopyTo(Bookmark[] array, int arrayIndex);
public abstract int Count { get; }
public abstract bool IsReadOnly { get; }
public abstract bool Remove(Bookmark item);
public abstract IEnumerator<Bookmark> GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region IDisposable
public abstract void Dispose();
#endregion
#region Additional properties
public abstract void Add(int lineIndex, string bookmarkName);
public abstract void Add(int lineIndex);
public abstract bool Contains(int lineIndex);
public abstract bool Remove(int lineIndex);
public abstract Bookmark GetBookmark(int i);
#endregion
}
/// <summary>
/// Collection of bookmarks
/// </summary>
public class Bookmarks : BaseBookmarks
{
protected FastColoredTextBox tb;
protected List<Bookmark> items = new List<Bookmark>();
protected int counter;
public Bookmarks(FastColoredTextBox tb)
{
this.tb = tb;
tb.LineInserted += tb_LineInserted;
tb.LineRemoved += tb_LineRemoved;
}
protected virtual void tb_LineRemoved(object sender, LineRemovedEventArgs e)
{
for(int i=0; i<Count; i++)
if (items[i].LineIndex >= e.Index)
{
if (items[i].LineIndex >= e.Index + e.Count)
{
items[i].LineIndex = items[i].LineIndex - e.Count;
continue;
}
var was = e.Index <= 0;
foreach (var b in items)
if (b.LineIndex == e.Index - 1)
was = true;
if(was)
{
items.RemoveAt(i);
i--;
}else
items[i].LineIndex = e.Index - 1;
//if (items[i].LineIndex == e.Index + e.Count - 1)
//{
// items[i].LineIndex = items[i].LineIndex - e.Count;
// continue;
//}
//
//items.RemoveAt(i);
//i--;
}
}
protected virtual void tb_LineInserted(object sender, LineInsertedEventArgs e)
{
for (int i = 0; i < Count; i++)
if (items[i].LineIndex >= e.Index)
{
items[i].LineIndex = items[i].LineIndex + e.Count;
}else
if (items[i].LineIndex == e.Index - 1 && e.Count == 1)
{
if(tb[e.Index - 1].StartSpacesCount == tb[e.Index - 1].Count)
items[i].LineIndex = items[i].LineIndex + e.Count;
}
}
public override void Dispose()
{
tb.LineInserted -= tb_LineInserted;
tb.LineRemoved -= tb_LineRemoved;
}
public override IEnumerator<Bookmark> GetEnumerator()
{
foreach (var item in items)
yield return item;
}
public override void Add(int lineIndex, string bookmarkName)
{
Add(new Bookmark(tb, bookmarkName ?? "Bookmark " + counter, lineIndex));
}
public override void Add(int lineIndex)
{
Add(new Bookmark(tb, "Bookmark " + counter, lineIndex));
}
public override void Clear()
{
items.Clear();
counter = 0;
}
public override void Add(Bookmark bookmark)
{
foreach (var bm in items)
if (bm.LineIndex == bookmark.LineIndex)
return;
items.Add(bookmark);
counter++;
tb.Invalidate();
}
public override bool Contains(Bookmark item)
{
return items.Contains(item);
}
public override bool Contains(int lineIndex)
{
foreach (var item in items)
if (item.LineIndex == lineIndex)
return true;
return false;
}
public override void CopyTo(Bookmark[] array, int arrayIndex)
{
items.CopyTo(array, arrayIndex);
}
public override int Count
{
get { return items.Count; }
}
public override bool IsReadOnly
{
get { return false; }
}
public override bool Remove(Bookmark item)
{
tb.Invalidate();
return items.Remove(item);
}
/// <summary>
/// Removes bookmark by line index
/// </summary>
public override bool Remove(int lineIndex)
{
bool was = false;
for (int i = 0; i < Count; i++)
if (items[i].LineIndex == lineIndex)
{
items.RemoveAt(i);
i--;
was = true;
}
tb.Invalidate();
return was;
}
/// <summary>
/// Returns Bookmark by index.
/// </summary>
public override Bookmark GetBookmark(int i)
{
return items[i];
}
}
/// <summary>
/// Bookmark of FastColoredTextbox
/// </summary>
public class Bookmark
{
public FastColoredTextBox TB { get; private set; }
/// <summary>
/// Name of bookmark
/// </summary>
public string Name { get; set; }
/// <summary>
/// Line index
/// </summary>
public int LineIndex {get; set; }
/// <summary>
/// Color of bookmark sign
/// </summary>
public Color Color { get; set; }
/// <summary>
/// Scroll textbox to the bookmark
/// </summary>
public virtual void DoVisible()
{
TB.Selection.Start = new Place(0, LineIndex);
TB.DoRangeVisible(TB.Selection, true);
TB.Invalidate();
}
public Bookmark(FastColoredTextBox tb, string name, int lineIndex)
{
this.TB = tb;
this.Name = name;
this.LineIndex = lineIndex;
Color = tb.BookmarkColor;
}
public virtual void Paint(Graphics gr, Rectangle lineRect)
{
var size = TB.CharHeight - 1;
using (var brush = new LinearGradientBrush(new Rectangle(0, lineRect.Top, size, size), Color.White, Color, 45))
gr.FillEllipse(brush, 0, lineRect.Top, size, size);
using (var pen = new Pen(Color))
gr.DrawEllipse(pen, 0, lineRect.Top, size, size);
}
}
}

View file

@ -0,0 +1,26 @@
using System;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Char and style
/// </summary>
public struct Char
{
/// <summary>
/// Unicode character
/// </summary>
public char c;
/// <summary>
/// Style bit mask
/// </summary>
/// <remarks>Bit 1 in position n means that this char will rendering by FastColoredTextBox.Styles[n]</remarks>
public StyleIndex style;
public Char(char c)
{
this.c = c;
style = StyleIndex.None;
}
}
}

View file

@ -0,0 +1,239 @@
using System.Collections.Generic;
using System;
namespace FastColoredTextBoxNS
{
public class CommandManager
{
readonly int maxHistoryLength = 200;
LimitedStack<UndoableCommand> history;
Stack<UndoableCommand> redoStack = new Stack<UndoableCommand>();
public TextSource TextSource{ get; private set; }
public bool UndoRedoStackIsEnabled { get; set; }
public CommandManager(TextSource ts)
{
history = new LimitedStack<UndoableCommand>(maxHistoryLength);
TextSource = ts;
UndoRedoStackIsEnabled = true;
}
public virtual void ExecuteCommand(Command cmd)
{
if (disabledCommands > 0)
return;
//multirange ?
if (cmd.ts.CurrentTB.Selection.ColumnSelectionMode)
if (cmd is UndoableCommand)
//make wrapper
cmd = new MultiRangeCommand((UndoableCommand)cmd);
if (cmd is UndoableCommand)
{
//if range is ColumnRange, then create wrapper
(cmd as UndoableCommand).autoUndo = autoUndoCommands > 0;
history.Push(cmd as UndoableCommand);
}
try
{
cmd.Execute();
}
catch (ArgumentOutOfRangeException)
{
//OnTextChanging cancels enter of the text
if (cmd is UndoableCommand)
history.Pop();
}
//
if (!UndoRedoStackIsEnabled)
ClearHistory();
//
redoStack.Clear();
//
TextSource.CurrentTB.OnUndoRedoStateChanged();
}
public void Undo()
{
if (history.Count > 0)
{
var cmd = history.Pop();
//
BeginDisableCommands();//prevent text changing into handlers
try
{
cmd.Undo();
}
finally
{
EndDisableCommands();
}
//
redoStack.Push(cmd);
}
//undo next autoUndo command
if (history.Count > 0)
{
if (history.Peek().autoUndo)
Undo();
}
TextSource.CurrentTB.OnUndoRedoStateChanged();
}
protected int disabledCommands = 0;
private void EndDisableCommands()
{
disabledCommands--;
}
private void BeginDisableCommands()
{
disabledCommands++;
}
int autoUndoCommands = 0;
public void EndAutoUndoCommands()
{
autoUndoCommands--;
if (autoUndoCommands == 0)
if (history.Count > 0)
history.Peek().autoUndo = false;
}
public void BeginAutoUndoCommands()
{
autoUndoCommands++;
}
internal void ClearHistory()
{
history.Clear();
redoStack.Clear();
TextSource.CurrentTB.OnUndoRedoStateChanged();
}
internal void Redo()
{
if (redoStack.Count == 0)
return;
UndoableCommand cmd;
BeginDisableCommands();//prevent text changing into handlers
try
{
cmd = redoStack.Pop();
if (TextSource.CurrentTB.Selection.ColumnSelectionMode)
TextSource.CurrentTB.Selection.ColumnSelectionMode = false;
TextSource.CurrentTB.Selection.Start = cmd.sel.Start;
TextSource.CurrentTB.Selection.End = cmd.sel.End;
cmd.Execute();
history.Push(cmd);
}
finally
{
EndDisableCommands();
}
//redo command after autoUndoable command
if (cmd.autoUndo)
Redo();
TextSource.CurrentTB.OnUndoRedoStateChanged();
}
public bool UndoEnabled
{
get
{
return history.Count > 0;
}
}
public bool RedoEnabled
{
get
{
return redoStack.Count > 0;
}
}
}
public abstract class Command
{
public TextSource ts;
public abstract void Execute();
}
internal class RangeInfo
{
public Place Start { get; set; }
public Place End { get; set; }
public RangeInfo(Range r)
{
Start = r.Start;
End = r.End;
}
internal int FromX
{
get
{
if (End.iLine < Start.iLine) return End.iChar;
if (End.iLine > Start.iLine) return Start.iChar;
return Math.Min(End.iChar, Start.iChar);
}
}
}
public abstract class UndoableCommand : Command
{
internal RangeInfo sel;
internal RangeInfo lastSel;
internal bool autoUndo;
public UndoableCommand(TextSource ts)
{
this.ts = ts;
sel = new RangeInfo(ts.CurrentTB.Selection);
}
public virtual void Undo()
{
OnTextChanged(true);
}
public override void Execute()
{
lastSel = new RangeInfo(ts.CurrentTB.Selection);
OnTextChanged(false);
}
protected virtual void OnTextChanged(bool invert)
{
bool b = sel.Start.iLine < lastSel.Start.iLine;
if (invert)
{
if (b)
ts.OnTextChanged(sel.Start.iLine, sel.Start.iLine);
else
ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine);
}
else
{
if (b)
ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine);
else
ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine);
}
}
public abstract UndoableCommand Clone();
}
}

View file

@ -0,0 +1,809 @@
using System;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Insert single char
/// </summary>
/// <remarks>This operation includes also insertion of new line and removing char by backspace</remarks>
public class InsertCharCommand : UndoableCommand
{
public char c;
char deletedChar = '\x0';
/// <summary>
/// Constructor
/// </summary>
/// <param name="tb">Underlaying textbox</param>
/// <param name="c">Inserting char</param>
public InsertCharCommand(TextSource ts, char c): base(ts)
{
this.c = c;
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
ts.OnTextChanging();
switch (c)
{
case '\n': MergeLines(sel.Start.iLine, ts); break;
case '\r': break;
case '\b':
ts.CurrentTB.Selection.Start = lastSel.Start;
char cc = '\x0';
if (deletedChar != '\x0')
{
ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine);
InsertChar(deletedChar, ref cc, ts);
}
break;
case '\t':
ts.CurrentTB.ExpandBlock(sel.Start.iLine);
for (int i = sel.FromX; i < lastSel.FromX; i++)
ts[sel.Start.iLine].RemoveAt(sel.Start.iChar);
ts.CurrentTB.Selection.Start = sel.Start;
break;
default:
ts.CurrentTB.ExpandBlock(sel.Start.iLine);
ts[sel.Start.iLine].RemoveAt(sel.Start.iChar);
ts.CurrentTB.Selection.Start = sel.Start;
break;
}
ts.NeedRecalc(new TextSource.TextChangedEventArgs(sel.Start.iLine, sel.Start.iLine));
base.Undo();
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine);
string s = c.ToString();
ts.OnTextChanging(ref s);
if (s.Length == 1)
c = s[0];
if (String.IsNullOrEmpty(s))
throw new ArgumentOutOfRangeException();
if (ts.Count == 0)
InsertLine(ts);
InsertChar(c, ref deletedChar, ts);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(ts.CurrentTB.Selection.Start.iLine, ts.CurrentTB.Selection.Start.iLine));
base.Execute();
}
internal static void InsertChar(char c, ref char deletedChar, TextSource ts)
{
var tb = ts.CurrentTB;
switch (c)
{
case '\n':
if (!ts.CurrentTB.AllowInsertRemoveLines)
throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode");
if (ts.Count == 0)
InsertLine(ts);
InsertLine(ts);
break;
case '\r': break;
case '\b'://backspace
if (tb.Selection.Start.iChar == 0 && tb.Selection.Start.iLine == 0)
return;
if (tb.Selection.Start.iChar == 0)
{
if (!ts.CurrentTB.AllowInsertRemoveLines)
throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode");
if (tb.LineInfos[tb.Selection.Start.iLine - 1].VisibleState != VisibleState.Visible)
tb.ExpandBlock(tb.Selection.Start.iLine - 1);
deletedChar = '\n';
MergeLines(tb.Selection.Start.iLine - 1, ts);
}
else
{
deletedChar = ts[tb.Selection.Start.iLine][tb.Selection.Start.iChar - 1].c;
ts[tb.Selection.Start.iLine].RemoveAt(tb.Selection.Start.iChar - 1);
tb.Selection.Start = new Place(tb.Selection.Start.iChar - 1, tb.Selection.Start.iLine);
}
break;
case '\t':
int spaceCountNextTabStop = tb.TabLength - (tb.Selection.Start.iChar % tb.TabLength);
if (spaceCountNextTabStop == 0)
spaceCountNextTabStop = tb.TabLength;
for (int i = 0; i < spaceCountNextTabStop; i++)
ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(' '));
tb.Selection.Start = new Place(tb.Selection.Start.iChar + spaceCountNextTabStop, tb.Selection.Start.iLine);
break;
default:
ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(c));
tb.Selection.Start = new Place(tb.Selection.Start.iChar + 1, tb.Selection.Start.iLine);
break;
}
}
internal static void InsertLine(TextSource ts)
{
var tb = ts.CurrentTB;
if (!tb.Multiline && tb.LinesCount > 0)
return;
if (ts.Count == 0)
ts.InsertLine(0, ts.CreateLine());
else
BreakLines(tb.Selection.Start.iLine, tb.Selection.Start.iChar, ts);
tb.Selection.Start = new Place(0, tb.Selection.Start.iLine + 1);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
/// <summary>
/// Merge lines i and i+1
/// </summary>
internal static void MergeLines(int i, TextSource ts)
{
var tb = ts.CurrentTB;
if (i + 1 >= ts.Count)
return;
tb.ExpandBlock(i);
tb.ExpandBlock(i + 1);
int pos = ts[i].Count;
//
/*
if(ts[i].Count == 0)
ts.RemoveLine(i);
else*/
if (ts[i + 1].Count == 0)
ts.RemoveLine(i + 1);
else
{
ts[i].AddRange(ts[i + 1]);
ts.RemoveLine(i + 1);
}
tb.Selection.Start = new Place(pos, i);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
internal static void BreakLines(int iLine, int pos, TextSource ts)
{
Line newLine = ts.CreateLine();
for(int i=pos;i<ts[iLine].Count;i++)
newLine.Add(ts[iLine][i]);
ts[iLine].RemoveRange(pos, ts[iLine].Count - pos);
//
ts.InsertLine(iLine+1, newLine);
}
public override UndoableCommand Clone()
{
return new InsertCharCommand(ts, c);
}
}
/// <summary>
/// Insert text
/// </summary>
public class InsertTextCommand : UndoableCommand
{
public string InsertedText;
/// <summary>
/// Constructor
/// </summary>
/// <param name="tb">Underlaying textbox</param>
/// <param name="insertedText">Text for inserting</param>
public InsertTextCommand(TextSource ts, string insertedText): base(ts)
{
this.InsertedText = insertedText;
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
ts.CurrentTB.Selection.Start = sel.Start;
ts.CurrentTB.Selection.End = lastSel.Start;
ts.OnTextChanging();
ClearSelectedCommand.ClearSelected(ts);
base.Undo();
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
ts.OnTextChanging(ref InsertedText);
InsertText(InsertedText, ts);
base.Execute();
}
internal static void InsertText(string insertedText, TextSource ts)
{
var tb = ts.CurrentTB;
try
{
tb.Selection.BeginUpdate();
char cc = '\x0';
if (ts.Count == 0)
{
InsertCharCommand.InsertLine(ts);
tb.Selection.Start = Place.Empty;
}
tb.ExpandBlock(tb.Selection.Start.iLine);
var len = insertedText.Length;
for (int i = 0; i < len; i++)
{
var c = insertedText[i];
if(c == '\r' && (i >= len - 1 || insertedText[i + 1] != '\n'))
InsertCharCommand.InsertChar('\n', ref cc, ts);
else
InsertCharCommand.InsertChar(c, ref cc, ts);
}
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
finally {
tb.Selection.EndUpdate();
}
}
public override UndoableCommand Clone()
{
return new InsertTextCommand(ts, InsertedText);
}
}
/// <summary>
/// Insert text into given ranges
/// </summary>
public class ReplaceTextCommand : UndoableCommand
{
string insertedText;
List<Range> ranges;
List<string> prevText = new List<string>();
/// <summary>
/// Constructor
/// </summary>
/// <param name="tb">Underlaying textbox</param>
/// <param name="ranges">List of ranges for replace</param>
/// <param name="insertedText">Text for inserting</param>
public ReplaceTextCommand(TextSource ts, List<Range> ranges, string insertedText)
: base(ts)
{
//sort ranges by place
ranges.Sort((r1, r2)=>
{
if (r1.Start.iLine == r2.Start.iLine)
return r1.Start.iChar.CompareTo(r2.Start.iChar);
return r1.Start.iLine.CompareTo(r2.Start.iLine);
});
//
this.ranges = ranges;
this.insertedText = insertedText;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.BeginUpdate();
tb.Selection.BeginUpdate();
for (int i = 0; i<ranges.Count; i++)
{
tb.Selection.Start = ranges[i].Start;
for (int j = 0; j < insertedText.Length; j++)
tb.Selection.GoRight(true);
ClearSelected(ts);
InsertTextCommand.InsertText(prevText[prevText.Count - i - 1], ts);
}
tb.Selection.EndUpdate();
tb.EndUpdate();
if (ranges.Count > 0)
ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine);
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging(ref insertedText);
tb.Selection.BeginUpdate();
tb.BeginUpdate();
for (int i = ranges.Count - 1; i >= 0; i--)
{
tb.Selection.Start = ranges[i].Start;
tb.Selection.End = ranges[i].End;
prevText.Add(tb.Selection.Text);
ClearSelected(ts);
if (insertedText != "")
InsertTextCommand.InsertText(insertedText, ts);
}
if(ranges.Count > 0)
ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine);
tb.EndUpdate();
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new ReplaceTextCommand(ts, new List<Range>(ranges), insertedText);
}
internal static void ClearSelected(TextSource ts)
{
var tb = ts.CurrentTB;
tb.Selection.Normalize();
Place start = tb.Selection.Start;
Place end = tb.Selection.End;
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = tb.Selection.FromX;
int toChar = tb.Selection.ToX;
if (fromLine < 0) return;
//
if (fromLine == toLine)
ts[fromLine].RemoveRange(fromChar, toChar - fromChar);
else
{
ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar);
ts[toLine].RemoveRange(0, toChar);
ts.RemoveLine(fromLine + 1, toLine - fromLine - 1);
InsertCharCommand.MergeLines(fromLine, ts);
}
}
}
/// <summary>
/// Clear selected text
/// </summary>
public class ClearSelectedCommand : UndoableCommand
{
string deletedText;
/// <summary>
/// Construstor
/// </summary>
/// <param name="tb">Underlaying textbox</param>
public ClearSelectedCommand(TextSource ts): base(ts)
{
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
ts.CurrentTB.Selection.Start = new Place(sel.FromX, Math.Min(sel.Start.iLine, sel.End.iLine));
ts.OnTextChanging();
InsertTextCommand.InsertText(deletedText, ts);
ts.OnTextChanged(sel.Start.iLine, sel.End.iLine);
ts.CurrentTB.Selection.Start = sel.Start;
ts.CurrentTB.Selection.End = sel.End;
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
var tb = ts.CurrentTB;
string temp = null;
ts.OnTextChanging(ref temp);
if (temp == "")
throw new ArgumentOutOfRangeException();
deletedText = tb.Selection.Text;
ClearSelected(ts);
lastSel = new RangeInfo(tb.Selection);
ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine);
}
internal static void ClearSelected(TextSource ts)
{
var tb = ts.CurrentTB;
Place start = tb.Selection.Start;
Place end = tb.Selection.End;
int fromLine = Math.Min(end.iLine, start.iLine);
int toLine = Math.Max(end.iLine, start.iLine);
int fromChar = tb.Selection.FromX;
int toChar = tb.Selection.ToX;
if (fromLine < 0) return;
//
if (fromLine == toLine)
ts[fromLine].RemoveRange(fromChar, toChar - fromChar);
else
{
ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar);
ts[toLine].RemoveRange(0, toChar);
ts.RemoveLine(fromLine + 1, toLine - fromLine - 1);
InsertCharCommand.MergeLines(fromLine, ts);
}
//
tb.Selection.Start = new Place(fromChar, fromLine);
//
ts.NeedRecalc(new TextSource.TextChangedEventArgs(fromLine, toLine));
}
public override UndoableCommand Clone()
{
return new ClearSelectedCommand(ts);
}
}
/// <summary>
/// Replaces text
/// </summary>
public class ReplaceMultipleTextCommand : UndoableCommand
{
List<ReplaceRange> ranges;
List<string> prevText = new List<string>();
public class ReplaceRange
{
public Range ReplacedRange { get; set; }
public String ReplaceText { get; set; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="ts">Underlaying textsource</param>
/// <param name="ranges">List of ranges for replace</param>
public ReplaceMultipleTextCommand(TextSource ts, List<ReplaceRange> ranges)
: base(ts)
{
//sort ranges by place
ranges.Sort((r1, r2) =>
{
if (r1.ReplacedRange.Start.iLine == r2.ReplacedRange.Start.iLine)
return r1.ReplacedRange.Start.iChar.CompareTo(r2.ReplacedRange.Start.iChar);
return r1.ReplacedRange.Start.iLine.CompareTo(r2.ReplacedRange.Start.iLine);
});
//
this.ranges = ranges;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for (int i = 0; i < ranges.Count; i++)
{
tb.Selection.Start = ranges[i].ReplacedRange.Start;
for (int j = 0; j < ranges[i].ReplaceText.Length; j++)
tb.Selection.GoRight(true);
ClearSelectedCommand.ClearSelected(ts);
var prevTextIndex = ranges.Count - 1 - i;
InsertTextCommand.InsertText(prevText[prevTextIndex], ts);
ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.Start.iLine);
}
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for (int i = ranges.Count - 1; i >= 0; i--)
{
tb.Selection.Start = ranges[i].ReplacedRange.Start;
tb.Selection.End = ranges[i].ReplacedRange.End;
prevText.Add(tb.Selection.Text);
ClearSelectedCommand.ClearSelected(ts);
InsertTextCommand.InsertText(ranges[i].ReplaceText, ts);
ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.End.iLine);
}
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new ReplaceMultipleTextCommand(ts, new List<ReplaceRange>(ranges));
}
}
/// <summary>
/// Removes lines
/// </summary>
public class RemoveLinesCommand : UndoableCommand
{
List<int> iLines;
List<string> prevText = new List<string>();
/// <summary>
/// Constructor
/// </summary>
/// <param name="tb">Underlaying textbox</param>
/// <param name="ranges">List of ranges for replace</param>
/// <param name="insertedText">Text for inserting</param>
public RemoveLinesCommand(TextSource ts, List<int> iLines)
: base(ts)
{
//sort iLines
iLines.Sort();
//
this.iLines = iLines;
lastSel = sel = new RangeInfo(ts.CurrentTB.Selection);
}
/// <summary>
/// Undo operation
/// </summary>
public override void Undo()
{
var tb = ts.CurrentTB;
ts.OnTextChanging();
tb.Selection.BeginUpdate();
//tb.BeginUpdate();
for (int i = 0; i < iLines.Count; i++)
{
var iLine = iLines[i];
if(iLine < ts.Count)
tb.Selection.Start = new Place(0, iLine);
else
tb.Selection.Start = new Place(ts[ts.Count - 1].Count, ts.Count - 1);
InsertCharCommand.InsertLine(ts);
tb.Selection.Start = new Place(0, iLine);
var text = prevText[prevText.Count - i - 1];
InsertTextCommand.InsertText(text, ts);
ts[iLine].IsChanged = true;
if (iLine < ts.Count - 1)
ts[iLine + 1].IsChanged = true;
else
ts[iLine - 1].IsChanged = true;
if(text.Trim() != string.Empty)
ts.OnTextChanged(iLine, iLine);
}
//tb.EndUpdate();
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
}
/// <summary>
/// Execute operation
/// </summary>
public override void Execute()
{
var tb = ts.CurrentTB;
prevText.Clear();
ts.OnTextChanging();
tb.Selection.BeginUpdate();
for(int i = iLines.Count - 1; i >= 0; i--)
{
var iLine = iLines[i];
prevText.Add(ts[iLine].Text);//backward
ts.RemoveLine(iLine);
//ts.OnTextChanged(ranges[i].Start.iLine, ranges[i].End.iLine);
}
tb.Selection.Start = new Place(0, 0);
tb.Selection.EndUpdate();
ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1));
lastSel = new RangeInfo(tb.Selection);
}
public override UndoableCommand Clone()
{
return new RemoveLinesCommand(ts, new List<int>(iLines));
}
}
/// <summary>
/// Wrapper for multirange commands
/// </summary>
public class MultiRangeCommand : UndoableCommand
{
private UndoableCommand cmd;
private Range range;
private List<UndoableCommand> commandsByRanges = new List<UndoableCommand>();
public MultiRangeCommand(UndoableCommand command):base(command.ts)
{
this.cmd = command;
range = ts.CurrentTB.Selection.Clone();
}
public override void Execute()
{
commandsByRanges.Clear();
var prevSelection = range.Clone();
var iChar = -1;
var iStartLine = prevSelection.Start.iLine;
var iEndLine = prevSelection.End.iLine;
ts.CurrentTB.Selection.ColumnSelectionMode = false;
ts.CurrentTB.Selection.BeginUpdate();
ts.CurrentTB.BeginUpdate();
ts.CurrentTB.AllowInsertRemoveLines = false;
try
{
if (cmd is InsertTextCommand)
ExecuteInsertTextCommand(ref iChar, (cmd as InsertTextCommand).InsertedText);
else
if (cmd is InsertCharCommand && (cmd as InsertCharCommand).c != '\x0' && (cmd as InsertCharCommand).c != '\b')//if not DEL or BACKSPACE
ExecuteInsertTextCommand(ref iChar, (cmd as InsertCharCommand).c.ToString());
else
ExecuteCommand(ref iChar);
}
catch (ArgumentOutOfRangeException)
{
}
finally
{
ts.CurrentTB.AllowInsertRemoveLines = true;
ts.CurrentTB.EndUpdate();
ts.CurrentTB.Selection = range;
if (iChar >= 0)
{
ts.CurrentTB.Selection.Start = new Place(iChar, iStartLine);
ts.CurrentTB.Selection.End = new Place(iChar, iEndLine);
}
ts.CurrentTB.Selection.ColumnSelectionMode = true;
ts.CurrentTB.Selection.EndUpdate();
}
}
private void ExecuteInsertTextCommand(ref int iChar, string text)
{
var lines = text.Split('\n');
var iLine = 0;
foreach (var r in range.GetSubRanges(true))
{
var line = ts.CurrentTB[r.Start.iLine];
var lineIsEmpty = r.End < r.Start && line.StartSpacesCount == line.Count;
if (!lineIsEmpty)
{
var insertedText = lines[iLine%lines.Length];
if (r.End < r.Start && insertedText!="")
{
//add forwarding spaces
insertedText = new string(' ', r.Start.iChar - r.End.iChar) + insertedText;
r.Start = r.End;
}
ts.CurrentTB.Selection = r;
var c = new InsertTextCommand(ts, insertedText);
c.Execute();
if (ts.CurrentTB.Selection.End.iChar > iChar)
iChar = ts.CurrentTB.Selection.End.iChar;
commandsByRanges.Add(c);
}
iLine++;
}
}
private void ExecuteCommand(ref int iChar)
{
foreach (var r in range.GetSubRanges(false))
{
ts.CurrentTB.Selection = r;
var c = cmd.Clone();
c.Execute();
if (ts.CurrentTB.Selection.End.iChar > iChar)
iChar = ts.CurrentTB.Selection.End.iChar;
commandsByRanges.Add(c);
}
}
public override void Undo()
{
ts.CurrentTB.BeginUpdate();
ts.CurrentTB.Selection.BeginUpdate();
try
{
for (int i = commandsByRanges.Count - 1; i >= 0; i--)
commandsByRanges[i].Undo();
}
finally
{
ts.CurrentTB.Selection.EndUpdate();
ts.CurrentTB.EndUpdate();
}
ts.CurrentTB.Selection = range.Clone();
ts.CurrentTB.OnTextChanged(range);
ts.CurrentTB.OnSelectionChanged();
ts.CurrentTB.Selection.ColumnSelectionMode = true;
}
public override UndoableCommand Clone()
{
throw new NotImplementedException();
}
}
/// <summary>
/// Remembers current selection and restore it after Undo
/// </summary>
public class SelectCommand : UndoableCommand
{
public SelectCommand(TextSource ts):base(ts)
{
}
public override void Execute()
{
//remember selection
lastSel = new RangeInfo(ts.CurrentTB.Selection);
}
protected override void OnTextChanged(bool invert)
{
}
public override void Undo()
{
//restore selection
ts.CurrentTB.Selection = new Range(ts.CurrentTB, lastSel.Start, lastSel.End);
}
public override UndoableCommand Clone()
{
var result = new SelectCommand(ts);
if(lastSel!=null)
result.lastSel = new RangeInfo(new Range(ts.CurrentTB, lastSel.Start, lastSel.End));
return result;
}
}
}

View file

@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Shows document map of FCTB
/// </summary>
public class DocumentMap : Control
{
public EventHandler TargetChanged;
FastColoredTextBox target;
private float scale = 0.3f;
private bool needRepaint = true;
private Place startPlace = Place.Empty;
private bool scrollbarVisible = true;
[Description("Target FastColoredTextBox")]
public FastColoredTextBox Target
{
get { return target; }
set
{
if (target != null)
UnSubscribe(target);
target = value;
if (value != null)
{
Subscribe(target);
}
OnTargetChanged();
}
}
/// <summary>
/// Scale
/// </summary>
[Description("Scale")]
[DefaultValue(0.3f)]
public new float Scale
{
get { return scale; }
set
{
scale = value;
NeedRepaint();
}
}
/// <summary>
/// Scrollbar visibility
/// </summary>
[Description("Scrollbar visibility")]
[DefaultValue(true)]
public bool ScrollbarVisible
{
get { return scrollbarVisible; }
set
{
scrollbarVisible = value;
NeedRepaint();
}
}
public DocumentMap()
{
ForeColor = Color.Maroon;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
Application.Idle += Application_Idle;
}
void Application_Idle(object sender, EventArgs e)
{
if(needRepaint)
Invalidate();
}
protected virtual void OnTargetChanged()
{
NeedRepaint();
if (TargetChanged != null)
TargetChanged(this, EventArgs.Empty);
}
protected virtual void UnSubscribe(FastColoredTextBox target)
{
target.Scroll -= new ScrollEventHandler(Target_Scroll);
target.SelectionChangedDelayed -= new EventHandler(Target_SelectionChanged);
target.VisibleRangeChanged -= new EventHandler(Target_VisibleRangeChanged);
}
protected virtual void Subscribe(FastColoredTextBox target)
{
target.Scroll += new ScrollEventHandler(Target_Scroll);
target.SelectionChangedDelayed += new EventHandler(Target_SelectionChanged);
target.VisibleRangeChanged += new EventHandler(Target_VisibleRangeChanged);
}
protected virtual void Target_VisibleRangeChanged(object sender, EventArgs e)
{
NeedRepaint();
}
protected virtual void Target_SelectionChanged(object sender, EventArgs e)
{
NeedRepaint();
}
protected virtual void Target_Scroll(object sender, ScrollEventArgs e)
{
NeedRepaint();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
NeedRepaint();
}
public void NeedRepaint()
{
needRepaint = true;
}
protected override void OnPaint(PaintEventArgs e)
{
if (target == null)
return;
var zoom = this.Scale * 100 / target.Zoom;
if (zoom <= float.Epsilon)
return;
//calc startPlace
var r = target.VisibleRange;
if (startPlace.iLine > r.Start.iLine)
startPlace.iLine = r.Start.iLine;
else
{
var endP = target.PlaceToPoint(r.End);
endP.Offset(0, -(int)(ClientSize.Height / zoom) + target.CharHeight);
var pp = target.PointToPlace(endP);
if (pp.iLine > startPlace.iLine)
startPlace.iLine = pp.iLine;
}
startPlace.iChar = 0;
//calc scroll pos
var linesCount = target.Lines.Count;
var sp1 = (float)r.Start.iLine / linesCount;
var sp2 = (float)r.End.iLine / linesCount;
//scale graphics
e.Graphics.ScaleTransform(zoom, zoom);
//draw text
var size = new SizeF(ClientSize.Width / zoom, ClientSize.Height / zoom);
target.DrawText(e.Graphics, startPlace, size.ToSize());
//draw visible rect
var p0 = target.PlaceToPoint(startPlace);
var p1 = target.PlaceToPoint(r.Start);
var p2 = target.PlaceToPoint(r.End);
var y1 = p1.Y - p0.Y;
var y2 = p2.Y + target.CharHeight - p0.Y;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
using (var brush = new SolidBrush(Color.FromArgb(50, ForeColor)))
using (var pen = new Pen(brush, 1 / zoom))
{
var rect = new Rectangle(0, y1, (int)((ClientSize.Width - 1) / zoom), y2 - y1);
e.Graphics.FillRectangle(brush, rect);
e.Graphics.DrawRectangle(pen, rect);
}
//draw scrollbar
if (scrollbarVisible)
{
e.Graphics.ResetTransform();
e.Graphics.SmoothingMode = SmoothingMode.None;
using (var brush = new SolidBrush(Color.FromArgb(200, ForeColor)))
{
var rect = new RectangleF(ClientSize.Width - 3, ClientSize.Height*sp1, 2,
ClientSize.Height*(sp2 - sp1));
e.Graphics.FillRectangle(brush, rect);
}
}
needRepaint = false;
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
Scroll(e.Location);
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
Scroll(e.Location);
base.OnMouseMove(e);
}
private void Scroll(Point point)
{
if (target == null)
return;
var zoom = this.Scale*100/target.Zoom;
if (zoom <= float.Epsilon)
return;
var p0 = target.PlaceToPoint(startPlace);
p0 = new Point(0, p0.Y + (int) (point.Y/zoom));
var pp = target.PointToPlace(p0);
target.DoRangeVisible(new Range(target, pp, pp), true);
BeginInvoke((MethodInvoker)OnScroll);
}
private void OnScroll()
{
Refresh();
target.Refresh();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Application.Idle -= Application_Idle;
if (target != null)
UnSubscribe(target);
}
base.Dispose(disposing);
}
}
}

View file

@ -0,0 +1,363 @@
// Copyright Tao Klerks, 2010-2012, tao@klerks.biz
// Licensed under the modified BSD license.
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace FastColoredTextBoxNS
{
public static class EncodingDetector
{
const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements
public static Encoding DetectTextFileEncoding(string InputFilename)
{
using (FileStream textfileStream = File.OpenRead(InputFilename))
{
return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize);
}
}
public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize)
{
bool uselessBool = false;
return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool);
}
public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM)
{
Encoding encodingFound = null;
long originalPos = InputFileStream.Position;
InputFileStream.Position = 0;
//First read only what we need for BOM detection
byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length];
InputFileStream.Read(bomBytes, 0, bomBytes.Length);
encodingFound = DetectBOMBytes(bomBytes);
if (encodingFound != null)
{
InputFileStream.Position = originalPos;
HasBOM = true;
return encodingFound;
}
//BOM Detection failed, going for heuristics now.
// create sample byte array and populate it
byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize];
Array.Copy(bomBytes, sampleBytes, bomBytes.Length);
if (InputFileStream.Length > bomBytes.Length)
InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length);
InputFileStream.Position = originalPos;
//test byte array content
encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes);
HasBOM = false;
return encodingFound;
}
public static Encoding DetectBOMBytes(byte[] BOMBytes)
{
if (BOMBytes.Length < 2)
return null;
if (BOMBytes[0] == 0xff
&& BOMBytes[1] == 0xfe
&& (BOMBytes.Length < 4
|| BOMBytes[2] != 0
|| BOMBytes[3] != 0
)
)
return Encoding.Unicode;
if (BOMBytes[0] == 0xfe
&& BOMBytes[1] == 0xff
)
return Encoding.BigEndianUnicode;
if (BOMBytes.Length < 3)
return null;
if (BOMBytes[0] == 0xef && BOMBytes[1] == 0xbb && BOMBytes[2] == 0xbf)
return Encoding.UTF8;
if (BOMBytes[0] == 0x2b && BOMBytes[1] == 0x2f && BOMBytes[2] == 0x76)
return Encoding.UTF7;
if (BOMBytes.Length < 4)
return null;
if (BOMBytes[0] == 0xff && BOMBytes[1] == 0xfe && BOMBytes[2] == 0 && BOMBytes[3] == 0)
return Encoding.UTF32;
if (BOMBytes[0] == 0 && BOMBytes[1] == 0 && BOMBytes[2] == 0xfe && BOMBytes[3] == 0xff)
return Encoding.GetEncoding(12001);
return null;
}
public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes)
{
long oddBinaryNullsInSample = 0;
long evenBinaryNullsInSample = 0;
long suspiciousUTF8SequenceCount = 0;
long suspiciousUTF8BytesTotal = 0;
long likelyUSASCIIBytesInSample = 0;
//Cycle through, keeping count of binary null positions, possible UTF-8
// sequences from upper ranges of Windows-1252, and probable US-ASCII
// character counts.
long currentPos = 0;
int skipUTF8Bytes = 0;
while (currentPos < SampleBytes.Length)
{
//binary null distribution
if (SampleBytes[currentPos] == 0)
{
if (currentPos % 2 == 0)
evenBinaryNullsInSample++;
else
oddBinaryNullsInSample++;
}
//likely US-ASCII characters
if (IsCommonUSASCIIByte(SampleBytes[currentPos]))
likelyUSASCIIBytesInSample++;
//suspicious sequences (look like UTF-8)
if (skipUTF8Bytes == 0)
{
int lengthFound = DetectSuspiciousUTF8SequenceLength(SampleBytes, currentPos);
if (lengthFound > 0)
{
suspiciousUTF8SequenceCount++;
suspiciousUTF8BytesTotal += lengthFound;
skipUTF8Bytes = lengthFound - 1;
}
}
else
{
skipUTF8Bytes--;
}
currentPos++;
}
//1: UTF-16 LE - in english / european environments, this is usually characterized by a
// high proportion of odd binary nulls (starting at 0), with (as this is text) a low
// proportion of even binary nulls.
// The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
// 60% nulls where you do expect nulls) are completely arbitrary.
if (((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2
&& ((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
)
return Encoding.Unicode;
//2: UTF-16 BE - in english / european environments, this is usually characterized by a
// high proportion of even binary nulls (starting at 0), with (as this is text) a low
// proportion of odd binary nulls.
// The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
// 60% nulls where you do expect nulls) are completely arbitrary.
if (((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2
&& ((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
)
return Encoding.BigEndianUnicode;
//3: UTF-8 - Martin Dürst outlines a method for detecting whether something CAN be UTF-8 content
// using regexp, in his w3c.org unicode FAQ entry:
// http://www.w3.org/International/questions/qa-forms-utf-8
// adapted here for C#.
string potentiallyMangledString = Encoding.ASCII.GetString(SampleBytes);
Regex UTF8Validator = new Regex(@"\A("
+ @"[\x09\x0A\x0D\x20-\x7E]"
+ @"|[\xC2-\xDF][\x80-\xBF]"
+ @"|\xE0[\xA0-\xBF][\x80-\xBF]"
+ @"|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}"
+ @"|\xED[\x80-\x9F][\x80-\xBF]"
+ @"|\xF0[\x90-\xBF][\x80-\xBF]{2}"
+ @"|[\xF1-\xF3][\x80-\xBF]{3}"
+ @"|\xF4[\x80-\x8F][\x80-\xBF]{2}"
+ @")*\z");
if (UTF8Validator.IsMatch(potentiallyMangledString))
{
//Unfortunately, just the fact that it CAN be UTF-8 doesn't tell you much about probabilities.
//If all the characters are in the 0-127 range, no harm done, most western charsets are same as UTF-8 in these ranges.
//If some of the characters were in the upper range (western accented characters), however, they would likely be mangled to 2-byte by the UTF-8 encoding process.
// So, we need to play stats.
// The "Random" likelihood of any pair of randomly generated characters being one
// of these "suspicious" character sequences is:
// 128 / (256 * 256) = 0.2%.
//
// In western text data, that is SIGNIFICANTLY reduced - most text data stays in the <127
// character range, so we assume that more than 1 in 500,000 of these character
// sequences indicates UTF-8. The number 500,000 is completely arbitrary - so sue me.
//
// We can only assume these character sequences will be rare if we ALSO assume that this
// IS in fact western text - in which case the bulk of the UTF-8 encoded data (that is
// not already suspicious sequences) should be plain US-ASCII bytes. This, I
// arbitrarily decided, should be 80% (a random distribution, eg binary data, would yield
// approx 40%, so the chances of hitting this threshold by accident in random data are
// VERY low).
if ((suspiciousUTF8SequenceCount * 500000.0 / SampleBytes.Length >= 1) //suspicious sequences
&& (
//all suspicious, so cannot evaluate proportion of US-Ascii
SampleBytes.Length - suspiciousUTF8BytesTotal == 0
||
likelyUSASCIIBytesInSample * 1.0 / (SampleBytes.Length - suspiciousUTF8BytesTotal) >= 0.8
)
)
return Encoding.UTF8;
}
return null;
}
private static bool IsCommonUSASCIIByte(byte testByte)
{
if (testByte == 0x0A //lf
|| testByte == 0x0D //cr
|| testByte == 0x09 //tab
|| (testByte >= 0x20 && testByte <= 0x2F) //common punctuation
|| (testByte >= 0x30 && testByte <= 0x39) //digits
|| (testByte >= 0x3A && testByte <= 0x40) //common punctuation
|| (testByte >= 0x41 && testByte <= 0x5A) //capital letters
|| (testByte >= 0x5B && testByte <= 0x60) //common punctuation
|| (testByte >= 0x61 && testByte <= 0x7A) //lowercase letters
|| (testByte >= 0x7B && testByte <= 0x7E) //common punctuation
)
return true;
else
return false;
}
private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos)
{
int lengthFound = 0;
if (SampleBytes.Length >= currentPos + 1
&& SampleBytes[currentPos] == 0xC2
)
{
if (SampleBytes[currentPos + 1] == 0x81
|| SampleBytes[currentPos + 1] == 0x8D
|| SampleBytes[currentPos + 1] == 0x8F
)
lengthFound = 2;
else if (SampleBytes[currentPos + 1] == 0x90
|| SampleBytes[currentPos + 1] == 0x9D
)
lengthFound = 2;
else if (SampleBytes[currentPos + 1] >= 0xA0
&& SampleBytes[currentPos + 1] <= 0xBF
)
lengthFound = 2;
}
else if (SampleBytes.Length >= currentPos + 1
&& SampleBytes[currentPos] == 0xC3
)
{
if (SampleBytes[currentPos + 1] >= 0x80
&& SampleBytes[currentPos + 1] <= 0xBF
)
lengthFound = 2;
}
else if (SampleBytes.Length >= currentPos + 1
&& SampleBytes[currentPos] == 0xC5
)
{
if (SampleBytes[currentPos + 1] == 0x92
|| SampleBytes[currentPos + 1] == 0x93
)
lengthFound = 2;
else if (SampleBytes[currentPos + 1] == 0xA0
|| SampleBytes[currentPos + 1] == 0xA1
)
lengthFound = 2;
else if (SampleBytes[currentPos + 1] == 0xB8
|| SampleBytes[currentPos + 1] == 0xBD
|| SampleBytes[currentPos + 1] == 0xBE
)
lengthFound = 2;
}
else if (SampleBytes.Length >= currentPos + 1
&& SampleBytes[currentPos] == 0xC6
)
{
if (SampleBytes[currentPos + 1] == 0x92)
lengthFound = 2;
}
else if (SampleBytes.Length >= currentPos + 1
&& SampleBytes[currentPos] == 0xCB
)
{
if (SampleBytes[currentPos + 1] == 0x86
|| SampleBytes[currentPos + 1] == 0x9C
)
lengthFound = 2;
}
else if (SampleBytes.Length >= currentPos + 2
&& SampleBytes[currentPos] == 0xE2
)
{
if (SampleBytes[currentPos + 1] == 0x80)
{
if (SampleBytes[currentPos + 2] == 0x93
|| SampleBytes[currentPos + 2] == 0x94
)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0x98
|| SampleBytes[currentPos + 2] == 0x99
|| SampleBytes[currentPos + 2] == 0x9A
)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0x9C
|| SampleBytes[currentPos + 2] == 0x9D
|| SampleBytes[currentPos + 2] == 0x9E
)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0xA0
|| SampleBytes[currentPos + 2] == 0xA1
|| SampleBytes[currentPos + 2] == 0xA2
)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0xA6)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0xB0)
lengthFound = 3;
if (SampleBytes[currentPos + 2] == 0xB9
|| SampleBytes[currentPos + 2] == 0xBA
)
lengthFound = 3;
}
else if (SampleBytes[currentPos + 1] == 0x82
&& SampleBytes[currentPos + 2] == 0xAC
)
lengthFound = 3;
else if (SampleBytes[currentPos + 1] == 0x84
&& SampleBytes[currentPos + 2] == 0xA2
)
lengthFound = 3;
}
return lengthFound;
}
}
}

View file

@ -0,0 +1,222 @@
using System.Text;
using System.Drawing;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Exports colored text as HTML
/// </summary>
/// <remarks>At this time only TextStyle renderer is supported. Other styles is not exported.</remarks>
public class ExportToHTML
{
public string LineNumbersCSS = "<style type=\"text/css\"> .lineNumber{font-family : monospace; font-size : small; font-style : normal; font-weight : normal; color : Teal; background-color : ThreedFace;} </style>";
/// <summary>
/// Use nbsp; instead space
/// </summary>
public bool UseNbsp { get; set; }
/// <summary>
/// Use nbsp; instead space in beginning of line
/// </summary>
public bool UseForwardNbsp { get; set; }
/// <summary>
/// Use original font
/// </summary>
public bool UseOriginalFont { get; set; }
/// <summary>
/// Use style tag instead style attribute
/// </summary>
public bool UseStyleTag { get; set; }
/// <summary>
/// Use 'br' tag instead of '\n'
/// </summary>
public bool UseBr { get; set; }
/// <summary>
/// Includes line numbers
/// </summary>
public bool IncludeLineNumbers { get; set; }
FastColoredTextBox tb;
public ExportToHTML()
{
UseNbsp = true;
UseOriginalFont = true;
UseStyleTag = true;
UseBr = true;
}
public string GetHtml(FastColoredTextBox tb)
{
this.tb = tb;
Range sel = new Range(tb);
sel.SelectAll();
return GetHtml(sel);
}
public string GetHtml(Range r)
{
this.tb = r.tb;
Dictionary<StyleIndex, object> styles = new Dictionary<StyleIndex, object>();
StringBuilder sb = new StringBuilder();
StringBuilder tempSB = new StringBuilder();
StyleIndex currentStyleId = StyleIndex.None;
r.Normalize();
int currentLine = r.Start.iLine;
styles[currentStyleId] = null;
//
if (UseOriginalFont)
sb.AppendFormat("<font style=\"font-family: {0}, monospace; font-size: {1}pt; line-height: {2}px;\">",
r.tb.Font.Name, r.tb.Font.SizeInPoints, r.tb.CharHeight);
//
if (IncludeLineNumbers)
tempSB.AppendFormat("<span class=lineNumber>{0}</span> ", currentLine + 1);
//
bool hasNonSpace = false;
foreach (Place p in r)
{
Char c = r.tb[p.iLine][p.iChar];
if (c.style != currentStyleId)
{
Flush(sb, tempSB, currentStyleId);
currentStyleId = c.style;
styles[currentStyleId] = null;
}
if (p.iLine != currentLine)
{
for (int i = currentLine; i < p.iLine; i++)
{
tempSB.Append(UseBr ? "<br>" : "\r\n");
if (IncludeLineNumbers)
tempSB.AppendFormat("<span class=lineNumber>{0}</span> ", i + 2);
}
currentLine = p.iLine;
hasNonSpace = false;
}
switch (c.c)
{
case ' ':
if ((hasNonSpace || !UseForwardNbsp) && !UseNbsp)
goto default;
tempSB.Append("&nbsp;");
break;
case '<':
tempSB.Append("&lt;");
break;
case '>':
tempSB.Append("&gt;");
break;
case '&':
tempSB.Append("&amp;");
break;
default:
hasNonSpace = true;
tempSB.Append(c.c);
break;
}
}
Flush(sb, tempSB, currentStyleId);
if (UseOriginalFont)
sb.Append("</font>");
//build styles
if (UseStyleTag)
{
tempSB.Length = 0;
tempSB.Append("<style type=\"text/css\">");
foreach (var styleId in styles.Keys)
tempSB.AppendFormat(".fctb{0}{{ {1} }}\r\n", GetStyleName(styleId), GetCss(styleId));
tempSB.Append("</style>");
sb.Insert(0, tempSB.ToString());
}
if (IncludeLineNumbers)
sb.Insert(0, LineNumbersCSS);
return sb.ToString();
}
private string GetCss(StyleIndex styleIndex)
{
List<Style> styles = new List<Style>();
//find text renderer
TextStyle textStyle = null;
int mask = 1;
bool hasTextStyle = false;
for (int i = 0; i < tb.Styles.Length; i++)
{
if (tb.Styles[i] != null && ((int)styleIndex & mask) != 0)
if (tb.Styles[i].IsExportable)
{
var style = tb.Styles[i];
styles.Add(style);
bool isTextStyle = style is TextStyle;
if (isTextStyle)
if (!hasTextStyle || tb.AllowSeveralTextStyleDrawing)
{
hasTextStyle = true;
textStyle = style as TextStyle;
}
}
mask = mask << 1;
}
//add TextStyle css
string result = "";
if (!hasTextStyle)
{
//draw by default renderer
result = tb.DefaultStyle.GetCSS();
}
else
{
result = textStyle.GetCSS();
}
//add no TextStyle css
foreach(var style in styles)
// if (style != textStyle)
if(!(style is TextStyle))
result += style.GetCSS();
return result;
}
public static string GetColorAsString(Color color)
{
if(color==Color.Transparent)
return "";
return string.Format("#{0:x2}{1:x2}{2:x2}", color.R, color.G, color.B);
}
string GetStyleName(StyleIndex styleIndex)
{
return styleIndex.ToString().Replace(" ", "").Replace(",", "");
}
private void Flush(StringBuilder sb, StringBuilder tempSB, StyleIndex currentStyle)
{
//find textRenderer
if (tempSB.Length == 0)
return;
if (UseStyleTag)
sb.AppendFormat("<font class=fctb{0}>{1}</font>", GetStyleName(currentStyle), tempSB.ToString());
else
{
string css = GetCss(currentStyle);
if(css!="")
sb.AppendFormat("<font style=\"{0}\">", css);
sb.Append(tempSB.ToString());
if (css != "")
sb.Append("</font>");
}
tempSB.Length = 0;
}
}
}

View file

@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Exports colored text as RTF
/// </summary>
/// <remarks>At this time only TextStyle renderer is supported. Other styles is not exported.</remarks>
public class ExportToRTF
{
/// <summary>
/// Includes line numbers
/// </summary>
public bool IncludeLineNumbers { get; set; }
/// <summary>
/// Use original font
/// </summary>
public bool UseOriginalFont { get; set; }
FastColoredTextBox tb;
Dictionary<Color, int> colorTable = new Dictionary<Color, int>();
public ExportToRTF()
{
UseOriginalFont = true;
}
public string GetRtf(FastColoredTextBox tb)
{
this.tb = tb;
Range sel = new Range(tb);
sel.SelectAll();
return GetRtf(sel);
}
public string GetRtf(Range r)
{
this.tb = r.tb;
var styles = new Dictionary<StyleIndex, object>();
var sb = new StringBuilder();
var tempSB = new StringBuilder();
var currentStyleId = StyleIndex.None;
r.Normalize();
int currentLine = r.Start.iLine;
styles[currentStyleId] = null;
colorTable.Clear();
//
var lineNumberColor = GetColorTableNumber(r.tb.LineNumberColor);
if (IncludeLineNumbers)
tempSB.AppendFormat(@"{{\cf{1} {0}}}\tab", currentLine + 1, lineNumberColor);
//
foreach (Place p in r)
{
Char c = r.tb[p.iLine][p.iChar];
if (c.style != currentStyleId)
{
Flush(sb, tempSB, currentStyleId);
currentStyleId = c.style;
styles[currentStyleId] = null;
}
if (p.iLine != currentLine)
{
for (int i = currentLine; i < p.iLine; i++)
{
tempSB.AppendLine(@"\line");
if (IncludeLineNumbers)
tempSB.AppendFormat(@"{{\cf{1} {0}}}\tab", i + 2, lineNumberColor);
}
currentLine = p.iLine;
}
switch (c.c)
{
case '\\':
tempSB.Append(@"\\");
break;
case '{':
tempSB.Append(@"\{");
break;
case '}':
tempSB.Append(@"\}");
break;
default:
var ch = c.c;
var code = (int)ch;
if(code < 128)
tempSB.Append(c.c);
else
tempSB.AppendFormat(@"{{\u{0}}}", code);
break;
}
}
Flush(sb, tempSB, currentStyleId);
//build color table
var list = new SortedList<int, Color>();
foreach (var pair in colorTable)
list.Add(pair.Value, pair.Key);
tempSB.Length = 0;
tempSB.AppendFormat(@"{{\colortbl;");
foreach (var pair in list)
tempSB.Append(GetColorAsString(pair.Value)+";");
tempSB.AppendLine("}");
//
if (UseOriginalFont)
{
sb.Insert(0, string.Format(@"{{\fonttbl{{\f0\fmodern {0};}}}}{{\fs{1} ",
tb.Font.Name, (int)(2 * tb.Font.SizeInPoints), tb.CharHeight));
sb.AppendLine(@"}");
}
sb.Insert(0, tempSB.ToString());
sb.Insert(0, @"{\rtf1\ud\deff0");
sb.AppendLine(@"}");
return sb.ToString();
}
private RTFStyleDescriptor GetRtfDescriptor(StyleIndex styleIndex)
{
List<Style> styles = new List<Style>();
//find text renderer
TextStyle textStyle = null;
int mask = 1;
bool hasTextStyle = false;
for (int i = 0; i < tb.Styles.Length; i++)
{
if (tb.Styles[i] != null && ((int)styleIndex & mask) != 0)
if (tb.Styles[i].IsExportable)
{
var style = tb.Styles[i];
styles.Add(style);
bool isTextStyle = style is TextStyle;
if (isTextStyle)
if (!hasTextStyle || tb.AllowSeveralTextStyleDrawing)
{
hasTextStyle = true;
textStyle = style as TextStyle;
}
}
mask = mask << 1;
}
//add TextStyle css
RTFStyleDescriptor result = null;
if (!hasTextStyle)
{
//draw by default renderer
result = tb.DefaultStyle.GetRTF();
}
else
{
result = textStyle.GetRTF();
}
return result;
}
public static string GetColorAsString(Color color)
{
if (color == Color.Transparent)
return "";
return string.Format(@"\red{0}\green{1}\blue{2}", color.R, color.G, color.B);
}
private void Flush(StringBuilder sb, StringBuilder tempSB, StyleIndex currentStyle)
{
//find textRenderer
if (tempSB.Length == 0)
return;
var desc = GetRtfDescriptor(currentStyle);
var cf = GetColorTableNumber(desc.ForeColor);
var cb = GetColorTableNumber(desc.BackColor);
var tags = new StringBuilder();
if (cf >= 0)
tags.AppendFormat(@"\cf{0}", cf);
if (cb >= 0)
tags.AppendFormat(@"\highlight{0}", cb);
if(!string.IsNullOrEmpty(desc.AdditionalTags))
tags.Append(desc.AdditionalTags.Trim());
if(tags.Length > 0)
sb.AppendFormat(@"{{{0} {1}}}", tags, tempSB.ToString());
else
sb.Append(tempSB.ToString());
tempSB.Length = 0;
}
private int GetColorTableNumber(Color color)
{
if (color.A == 0)
return -1;
if (!colorTable.ContainsKey(color))
colorTable[color] = colorTable.Count + 1;
return colorTable[color];
}
}
public class RTFStyleDescriptor
{
public Color ForeColor { get; set; }
public Color BackColor { get; set; }
public string AdditionalTags { get; set; }
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,453 @@
//#define debug
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace FastColoredTextBoxNS
{
/// <summary>
/// This class contains the source text (chars and styles).
/// It stores a text lines, the manager of commands, undo/redo stack, styles.
/// </summary>
public class FileTextSource : TextSource, IDisposable
{
List<int> sourceFileLinePositions = new List<int>();
FileStream fs;
Encoding fileEncoding;
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
/// <summary>
/// Occurs when need to display line in the textbox
/// </summary>
public event EventHandler<LineNeededEventArgs> LineNeeded;
/// <summary>
/// Occurs when need to save line in the file
/// </summary>
public event EventHandler<LinePushedEventArgs> LinePushed;
public FileTextSource(FastColoredTextBox currentTB)
: base(currentTB)
{
timer.Interval = 10000;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
SaveEOL = Environment.NewLine;
}
void timer_Tick(object sender, EventArgs e)
{
timer.Enabled = false;
try
{
UnloadUnusedLines();
}
finally
{
timer.Enabled = true;
}
}
private void UnloadUnusedLines()
{
const int margin = 2000;
var iStartVisibleLine = CurrentTB.VisibleRange.Start.iLine;
var iFinishVisibleLine = CurrentTB.VisibleRange.End.iLine;
int count = 0;
for (int i = 0; i < Count; i++)
if (base.lines[i] != null && !base.lines[i].IsChanged && Math.Abs(i - iFinishVisibleLine) > margin)
{
base.lines[i] = null;
count++;
}
#if debug
Console.WriteLine("UnloadUnusedLines: " + count);
#endif
}
public void OpenFile(string fileName, Encoding enc)
{
Clear();
if (fs != null)
fs.Dispose();
SaveEOL = Environment.NewLine;
//read lines of file
fs = new FileStream(fileName, FileMode.Open);
var length = fs.Length;
//read signature
enc = DefineEncoding(enc, fs);
int shift = DefineShift(enc);
//first line
sourceFileLinePositions.Add((int)fs.Position);
base.lines.Add(null);
//other lines
sourceFileLinePositions.Capacity = (int)(length/7 + 1000);
int prev = 0;
while(fs.Position < length)
{
var b = fs.ReadByte();
if (b == 10)// \n
{
sourceFileLinePositions.Add((int)(fs.Position) + shift);
base.lines.Add(null);
}else
if (prev == 13)// \r (Mac format)
{
sourceFileLinePositions.Add((int)(fs.Position - 1) + shift);
base.lines.Add(null);
SaveEOL = "\r";
}
prev = b;
}
if (prev == 13)
{
sourceFileLinePositions.Add((int)(fs.Position) + shift);
base.lines.Add(null);
}
if(length > 2000000)
GC.Collect();
Line[] temp = new Line[100];
var c = base.lines.Count;
base.lines.AddRange(temp);
base.lines.TrimExcess();
base.lines.RemoveRange(c, temp.Length);
int[] temp2 = new int[100];
c = base.lines.Count;
sourceFileLinePositions.AddRange(temp2);
sourceFileLinePositions.TrimExcess();
sourceFileLinePositions.RemoveRange(c, temp.Length);
fileEncoding = enc;
OnLineInserted(0, Count);
//load first lines for calc width of the text
var linesCount = Math.Min(lines.Count, CurrentTB.ClientRectangle.Height/CurrentTB.CharHeight);
for (int i = 0; i < linesCount; i++)
LoadLineFromSourceFile(i);
//
NeedRecalc(new TextChangedEventArgs(0, linesCount - 1));
if (CurrentTB.WordWrap)
OnRecalcWordWrap(new TextChangedEventArgs(0, linesCount - 1));
}
private int DefineShift(Encoding enc)
{
if (enc.IsSingleByte)
return 0;
if (enc.HeaderName == "unicodeFFFE")
return 0;//UTF16 BE
if (enc.HeaderName == "utf-16")
return 1;//UTF16 LE
if (enc.HeaderName == "utf-32BE")
return 0;//UTF32 BE
if (enc.HeaderName == "utf-32")
return 3;//UTF32 LE
return 0;
}
private static Encoding DefineEncoding(Encoding enc, FileStream fs)
{
int bytesPerSignature = 0;
byte[] signature = new byte[4];
int c = fs.Read(signature, 0, 4);
if (signature[0] == 0xFF && signature[1] == 0xFE && signature[2] == 0x00 && signature[3] == 0x00 && c >= 4)
{
enc = Encoding.UTF32;//UTF32 LE
bytesPerSignature = 4;
}
else
if (signature[0] == 0x00 && signature[1] == 0x00 && signature[2] == 0xFE && signature[3] == 0xFF)
{
enc = new UTF32Encoding(true, true);//UTF32 BE
bytesPerSignature = 4;
}
else
if (signature[0] == 0xEF && signature[1] == 0xBB && signature[2] == 0xBF)
{
enc = Encoding.UTF8;//UTF8
bytesPerSignature = 3;
}
else
if (signature[0] == 0xFE && signature[1] == 0xFF)
{
enc = Encoding.BigEndianUnicode;//UTF16 BE
bytesPerSignature = 2;
}
else
if (signature[0] == 0xFF && signature[1] == 0xFE)
{
enc = Encoding.Unicode;//UTF16 LE
bytesPerSignature = 2;
}
fs.Seek(bytesPerSignature, SeekOrigin.Begin);
return enc;
}
public void CloseFile()
{
if(fs!=null)
try
{
fs.Dispose();
}
catch
{
;
}
fs = null;
}
/// <summary>
/// End Of Line characters used for saving
/// </summary>
public string SaveEOL { get; set; }
public override void SaveToFile(string fileName, Encoding enc)
{
//
var newLinePos = new List<int>(Count);
//create temp file
var dir = Path.GetDirectoryName(fileName);
var tempFileName = Path.Combine(dir, Path.GetFileNameWithoutExtension(fileName) + ".tmp");
StreamReader sr = new StreamReader(fs, fileEncoding);
using (FileStream tempFs = new FileStream(tempFileName, FileMode.Create))
using (StreamWriter sw = new StreamWriter(tempFs, enc))
{
sw.Flush();
for (int i = 0; i < Count; i++)
{
newLinePos.Add((int)tempFs.Length);
var sourceLine = ReadLine(sr, i);//read line from source file
string line;
bool lineIsChanged = lines[i] != null && lines[i].IsChanged;
if (lineIsChanged)
line = lines[i].Text;
else
line = sourceLine;
//call event handler
if (LinePushed != null)
{
var args = new LinePushedEventArgs(sourceLine, i, lineIsChanged ? line : null);
LinePushed(this, args);
if(args.SavedText != null)
line = args.SavedText;
}
//save line to file
sw.Write(line);
if (i < Count - 1)
sw.Write(SaveEOL);
sw.Flush();
}
}
//clear lines buffer
for (int i = 0; i < Count; i++)
lines[i] = null;
//deattach from source file
sr.Dispose();
fs.Dispose();
//delete target file
if (File.Exists(fileName))
File.Delete(fileName);
//rename temp file
File.Move(tempFileName, fileName);
//binding to new file
sourceFileLinePositions = newLinePos;
fs = new FileStream(fileName, FileMode.Open);
this.fileEncoding = enc;
}
private string ReadLine(StreamReader sr, int i)
{
string line;
var filePos = sourceFileLinePositions[i];
if (filePos < 0)
return "";
fs.Seek(filePos, SeekOrigin.Begin);
sr.DiscardBufferedData();
line = sr.ReadLine();
return line;
}
public override void ClearIsChanged()
{
foreach (var line in lines)
if(line!=null)
line.IsChanged = false;
}
public override Line this[int i]
{
get
{
if (base.lines[i] != null)
return lines[i];
else
LoadLineFromSourceFile(i);
return lines[i];
}
set
{
throw new NotImplementedException();
}
}
private void LoadLineFromSourceFile(int i)
{
var line = CreateLine();
fs.Seek(sourceFileLinePositions[i], SeekOrigin.Begin);
StreamReader sr = new StreamReader(fs, fileEncoding);
var s = sr.ReadLine();
if (s == null)
s = "";
//call event handler
if(LineNeeded!=null)
{
var args = new LineNeededEventArgs(s, i);
LineNeeded(this, args);
s = args.DisplayedLineText;
if (s == null)
return;
}
foreach (var c in s)
line.Add(new Char(c));
base.lines[i] = line;
if (CurrentTB.WordWrap)
OnRecalcWordWrap(new TextChangedEventArgs(i, i));
}
public override void InsertLine(int index, Line line)
{
sourceFileLinePositions.Insert(index, -1);
base.InsertLine(index, line);
}
public override void RemoveLine(int index, int count)
{
sourceFileLinePositions.RemoveRange(index, count);
base.RemoveLine(index, count);
}
public override void Clear()
{
base.Clear();
}
public override int GetLineLength(int i)
{
if (base.lines[i] == null)
return 0;
else
return base.lines[i].Count;
}
public override bool LineHasFoldingStartMarker(int iLine)
{
if (lines[iLine] == null)
return false;
else
return !string.IsNullOrEmpty(lines[iLine].FoldingStartMarker);
}
public override bool LineHasFoldingEndMarker(int iLine)
{
if (lines[iLine] == null)
return false;
else
return !string.IsNullOrEmpty(lines[iLine].FoldingEndMarker);
}
public override void Dispose()
{
if (fs != null)
fs.Dispose();
timer.Dispose();
}
internal void UnloadLine(int iLine)
{
if (lines[iLine] != null && !lines[iLine].IsChanged)
lines[iLine] = null;
}
}
public class LineNeededEventArgs : EventArgs
{
public string SourceLineText { get; private set; }
public int DisplayedLineIndex { get; private set; }
/// <summary>
/// This text will be displayed in textbox
/// </summary>
public string DisplayedLineText { get; set; }
public LineNeededEventArgs(string sourceLineText, int displayedLineIndex)
{
this.SourceLineText = sourceLineText;
this.DisplayedLineIndex = displayedLineIndex;
this.DisplayedLineText = sourceLineText;
}
}
public class LinePushedEventArgs : EventArgs
{
public string SourceLineText { get; private set; }
public int DisplayedLineIndex { get; private set; }
/// <summary>
/// This property contains only changed text.
/// If text of line is not changed, this property contains null.
/// </summary>
public string DisplayedLineText { get; private set; }
/// <summary>
/// This text will be saved in the file
/// </summary>
public string SavedText { get; set; }
public LinePushedEventArgs(string sourceLineText, int displayedLineIndex, string displayedLineText)
{
this.SourceLineText = sourceLineText;
this.DisplayedLineIndex = displayedLineIndex;
this.DisplayedLineText = displayedLineText;
this.SavedText = displayedLineText;
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
public partial class FindForm : Form
{
bool firstSearch = true;
Place startPlace;
FastColoredTextBox tb;
public FindForm(FastColoredTextBox tb)
{
InitializeComponent();
this.tb = tb;
}
private void btClose_Click(object sender, EventArgs e)
{
Close();
}
private void btFindNext_Click(object sender, EventArgs e)
{
FindNext(tbFind.Text);
}
public virtual void FindNext(string pattern)
{
try
{
RegexOptions opt = cbMatchCase.Checked ? RegexOptions.None : RegexOptions.IgnoreCase;
if (!cbRegex.Checked)
pattern = Regex.Escape(pattern);
if (cbWholeWord.Checked)
pattern = "\\b" + pattern + "\\b";
//
Range range = tb.Selection.Clone();
range.Normalize();
//
if (firstSearch)
{
startPlace = range.Start;
firstSearch = false;
}
//
range.Start = range.End;
if (range.Start >= startPlace)
range.End = new Place(tb.GetLineLength(tb.LinesCount - 1), tb.LinesCount - 1);
else
range.End = startPlace;
//
foreach (var r in range.GetRangesByLines(pattern, opt))
{
tb.Selection = r;
tb.DoSelectionVisible();
tb.Invalidate();
return;
}
//
if (range.Start >= startPlace && startPlace > Place.Empty)
{
tb.Selection.Start = new Place(0, 0);
FindNext(pattern);
return;
}
MessageBox.Show("Not found");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void tbFind_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r')
{
btFindNext.PerformClick();
e.Handled = true;
return;
}
if (e.KeyChar == '\x1b')
{
Hide();
e.Handled = true;
return;
}
}
private void FindForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
this.tb.Focus();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape)
{
this.Close();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
protected override void OnActivated(EventArgs e)
{
tbFind.Focus();
ResetSerach();
}
void ResetSerach()
{
firstSearch = true;
}
private void cbMatchCase_CheckedChanged(object sender, EventArgs e)
{
ResetSerach();
}
}
}

View file

@ -0,0 +1,146 @@
namespace FastColoredTextBoxNS
{
partial class FindForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btClose = new System.Windows.Forms.Button();
this.btFindNext = new System.Windows.Forms.Button();
this.tbFind = new System.Windows.Forms.TextBox();
this.cbRegex = new System.Windows.Forms.CheckBox();
this.cbMatchCase = new System.Windows.Forms.CheckBox();
this.label1 = new System.Windows.Forms.Label();
this.cbWholeWord = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
//
// btClose
//
this.btClose.Location = new System.Drawing.Point(273, 73);
this.btClose.Name = "btClose";
this.btClose.Size = new System.Drawing.Size(75, 23);
this.btClose.TabIndex = 5;
this.btClose.Text = "Close";
this.btClose.UseVisualStyleBackColor = true;
this.btClose.Click += new System.EventHandler(this.btClose_Click);
//
// btFindNext
//
this.btFindNext.Location = new System.Drawing.Point(192, 73);
this.btFindNext.Name = "btFindNext";
this.btFindNext.Size = new System.Drawing.Size(75, 23);
this.btFindNext.TabIndex = 4;
this.btFindNext.Text = "Find next";
this.btFindNext.UseVisualStyleBackColor = true;
this.btFindNext.Click += new System.EventHandler(this.btFindNext_Click);
//
// tbFind
//
this.tbFind.Location = new System.Drawing.Point(42, 12);
this.tbFind.Name = "tbFind";
this.tbFind.Size = new System.Drawing.Size(306, 20);
this.tbFind.TabIndex = 0;
this.tbFind.TextChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
this.tbFind.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tbFind_KeyPress);
//
// cbRegex
//
this.cbRegex.AutoSize = true;
this.cbRegex.Location = new System.Drawing.Point(249, 38);
this.cbRegex.Name = "cbRegex";
this.cbRegex.Size = new System.Drawing.Size(57, 17);
this.cbRegex.TabIndex = 3;
this.cbRegex.Text = "Regex";
this.cbRegex.UseVisualStyleBackColor = true;
this.cbRegex.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// cbMatchCase
//
this.cbMatchCase.AutoSize = true;
this.cbMatchCase.Location = new System.Drawing.Point(42, 38);
this.cbMatchCase.Name = "cbMatchCase";
this.cbMatchCase.Size = new System.Drawing.Size(82, 17);
this.cbMatchCase.TabIndex = 1;
this.cbMatchCase.Text = "Match case";
this.cbMatchCase.UseVisualStyleBackColor = true;
this.cbMatchCase.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(6, 15);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(33, 13);
this.label1.TabIndex = 5;
this.label1.Text = "Find: ";
//
// cbWholeWord
//
this.cbWholeWord.AutoSize = true;
this.cbWholeWord.Location = new System.Drawing.Point(130, 38);
this.cbWholeWord.Name = "cbWholeWord";
this.cbWholeWord.Size = new System.Drawing.Size(113, 17);
this.cbWholeWord.TabIndex = 2;
this.cbWholeWord.Text = "Match whole word";
this.cbWholeWord.UseVisualStyleBackColor = true;
this.cbWholeWord.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// FindForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(360, 108);
this.Controls.Add(this.cbWholeWord);
this.Controls.Add(this.label1);
this.Controls.Add(this.cbMatchCase);
this.Controls.Add(this.cbRegex);
this.Controls.Add(this.tbFind);
this.Controls.Add(this.btFindNext);
this.Controls.Add(this.btClose);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "FindForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Find";
this.TopMost = true;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FindForm_FormClosing);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btClose;
private System.Windows.Forms.Button btFindNext;
private System.Windows.Forms.CheckBox cbRegex;
private System.Windows.Forms.CheckBox cbMatchCase;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox cbWholeWord;
public System.Windows.Forms.TextBox tbFind;
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,53 @@
using System;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
public partial class GoToForm : Form
{
public int SelectedLineNumber { get; set; }
public int TotalLineCount { get; set; }
public GoToForm()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.tbLineNumber.Text = this.SelectedLineNumber.ToString();
this.label.Text = String.Format("Line number (1 - {0}):", this.TotalLineCount);
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
this.tbLineNumber.Focus();
}
private void btnOk_Click(object sender, EventArgs e)
{
int enteredLine;
if (int.TryParse(this.tbLineNumber.Text, out enteredLine))
{
enteredLine = Math.Min(enteredLine, this.TotalLineCount);
enteredLine = Math.Max(1, enteredLine);
this.SelectedLineNumber = enteredLine;
}
this.DialogResult = DialogResult.OK;
this.Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}
}

View file

@ -0,0 +1,110 @@
namespace FastColoredTextBoxNS
{
partial class GoToForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label = new System.Windows.Forms.Label();
this.tbLineNumber = new System.Windows.Forms.TextBox();
this.btnOk = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label
//
this.label.AutoSize = true;
this.label.Location = new System.Drawing.Point(12, 9);
this.label.Name = "label";
this.label.Size = new System.Drawing.Size(96, 13);
this.label.TabIndex = 0;
this.label.Text = "Line Number (1/1):";
//
// tbLineNumber
//
this.tbLineNumber.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tbLineNumber.Location = new System.Drawing.Point(12, 29);
this.tbLineNumber.Name = "tbLineNumber";
this.tbLineNumber.Size = new System.Drawing.Size(296, 20);
this.tbLineNumber.TabIndex = 1;
//
// btnOk
//
this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnOk.Location = new System.Drawing.Point(152, 71);
this.btnOk.Name = "btnOk";
this.btnOk.Size = new System.Drawing.Size(75, 23);
this.btnOk.TabIndex = 2;
this.btnOk.Text = "OK";
this.btnOk.UseVisualStyleBackColor = true;
this.btnOk.Click += new System.EventHandler(this.btnOk_Click);
//
// btnCancel
//
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(233, 71);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// GoToForm
//
this.AcceptButton = this.btnOk;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(320, 106);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnOk);
this.Controls.Add(this.tbLineNumber);
this.Controls.Add(this.label);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "GoToForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Go To Line";
this.TopMost = true;
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label;
private System.Windows.Forms.TextBox tbLineNumber;
private System.Windows.Forms.Button btnOk;
private System.Windows.Forms.Button btnCancel;
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,364 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Collection of Hints.
/// This is temporary buffer for currently displayed hints.
/// </summary>
public class Hints : ICollection<Hint>, IDisposable
{
FastColoredTextBox tb;
List<Hint> items = new List<Hint>();
public Hints(FastColoredTextBox tb)
{
this.tb = tb;
tb.TextChanged += OnTextBoxTextChanged;
tb.KeyDown += OnTextBoxKeyDown;
tb.VisibleRangeChanged += OnTextBoxVisibleRangeChanged;
}
protected virtual void OnTextBoxKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == System.Windows.Forms.Keys.Escape && e.Modifiers == System.Windows.Forms.Keys.None)
Clear();
}
protected virtual void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
Clear();
}
public void Dispose()
{
tb.TextChanged -= OnTextBoxTextChanged;
tb.KeyDown -= OnTextBoxKeyDown;
tb.VisibleRangeChanged -= OnTextBoxVisibleRangeChanged;
}
void OnTextBoxVisibleRangeChanged(object sender, EventArgs e)
{
if (items.Count == 0)
return;
tb.NeedRecalc(true);
foreach (var item in items)
{
LayoutHint(item);
item.HostPanel.Invalidate();
}
}
private void LayoutHint(Hint hint)
{
if (hint.Inline || hint.Range.Start.iLine >= tb.LinesCount - 1)
{
if (hint.Range.Start.iLine < tb.LineInfos.Count - 1)
hint.HostPanel.Top = tb.LineInfos[hint.Range.Start.iLine + 1].startY - hint.TopPadding - hint.HostPanel.Height - tb.VerticalScroll.Value;
else
hint.HostPanel.Top = tb.TextHeight + tb.Paddings.Top - hint.HostPanel.Height - tb.VerticalScroll.Value;
}
else
{
hint.HostPanel.Top = tb.LineInfos[hint.Range.Start.iLine + 1].startY - tb.VerticalScroll.Value;
}
if (hint.Dock == DockStyle.Fill)
{
hint.Width = tb.ClientSize.Width - tb.LeftIndent - 2;
hint.HostPanel.Left = tb.LeftIndent;
}
else
{
var p1 = tb.PlaceToPoint(hint.Range.Start);
var p2 = tb.PlaceToPoint(hint.Range.End);
var cx = (p1.X + p2.X) / 2;
hint.HostPanel.Left = Math.Max( tb.LeftIndent, cx - hint.HostPanel.Width / 2);
}
}
public IEnumerator<Hint> GetEnumerator()
{
foreach (var item in items)
yield return item;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Clears all displayed hints
/// </summary>
public void Clear()
{
items.Clear();
if (tb.Controls.Count != 0)
{
var toDelete = new List<Control>();
foreach (Control item in tb.Controls)
if (item is UnfocusablePanel)
toDelete.Add(item);
foreach (var item in toDelete)
tb.Controls.Remove(item);
for (int i = 0; i < tb.LineInfos.Count; i++)
{
var li = tb.LineInfos[i];
li.bottomPadding = 0;
tb.LineInfos[i] = li;
}
tb.NeedRecalc();
tb.Invalidate();
tb.Select();
tb.ActiveControl = null;
}
}
/// <summary>
/// Add and shows the hint
/// </summary>
/// <param name="hint"></param>
public void Add(Hint hint)
{
items.Add(hint);
if (hint.Inline || hint.Range.Start.iLine >= tb.LinesCount - 1)
{
var li = tb.LineInfos[hint.Range.Start.iLine];
hint.TopPadding = li.bottomPadding;
li.bottomPadding += hint.HostPanel.Height;
tb.LineInfos[hint.Range.Start.iLine] = li;
tb.NeedRecalc(true);
}
LayoutHint(hint);
tb.OnVisibleRangeChanged();
hint.HostPanel.Parent = tb;
tb.Select();
tb.ActiveControl = null;
tb.Invalidate();
}
/// <summary>
/// Is collection contains the hint?
/// </summary>
public bool Contains(Hint item)
{
return items.Contains(item);
}
public void CopyTo(Hint[] array, int arrayIndex)
{
items.CopyTo(array, arrayIndex);
}
/// <summary>
/// Count of hints
/// </summary>
public int Count
{
get { return items.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(Hint item)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Hint of FastColoredTextbox
/// </summary>
public class Hint
{
/// <summary>
/// Text of simple hint
/// </summary>
public string Text { get { return HostPanel.Text; } set { HostPanel.Text = value; } }
/// <summary>
/// Linked range
/// </summary>
public Range Range { get; set; }
/// <summary>
/// Backcolor
/// </summary>
public Color BackColor { get { return HostPanel.BackColor; } set { HostPanel.BackColor = value; } }
/// <summary>
/// Second backcolor
/// </summary>
public Color BackColor2 { get { return HostPanel.BackColor2; } set { HostPanel.BackColor2 = value; } }
/// <summary>
/// Border color
/// </summary>
public Color BorderColor { get { return HostPanel.BorderColor; } set { HostPanel.BorderColor = value; } }
/// <summary>
/// Fore color
/// </summary>
public Color ForeColor { get { return HostPanel.ForeColor; } set { HostPanel.ForeColor = value; } }
/// <summary>
/// Text alignment
/// </summary>
public StringAlignment TextAlignment { get { return HostPanel.TextAlignment; } set { HostPanel.TextAlignment = value; } }
/// <summary>
/// Font
/// </summary>
public Font Font { get { return HostPanel.Font; } set { HostPanel.Font = value; } }
/// <summary>
/// Occurs when user click on simple hint
/// </summary>
public event EventHandler Click
{
add { HostPanel.Click += value; }
remove { HostPanel.Click -= value; }
}
/// <summary>
/// Inner control
/// </summary>
public Control InnerControl { get; set; }
/// <summary>
/// Docking (allows None and Fill only)
/// </summary>
public DockStyle Dock { get; set; }
/// <summary>
/// Width of hint (if Dock is None)
/// </summary>
public int Width { get { return HostPanel.Width; } set { HostPanel.Width = value; } }
/// <summary>
/// Height of hint
/// </summary>
public int Height { get { return HostPanel.Height; } set { HostPanel.Height = value; } }
/// <summary>
/// Host panel
/// </summary>
public UnfocusablePanel HostPanel { get; private set; }
internal int TopPadding { get; set; }
/// <summary>
/// Tag
/// </summary>
public object Tag { get; set; }
/// <summary>
/// Cursor
/// </summary>
public Cursor Cursor { get { return HostPanel.Cursor; } set { HostPanel.Cursor = value; } }
/// <summary>
/// Inlining. If True then hint will moves apart text.
/// </summary>
public bool Inline{get; set;}
/// <summary>
/// Scroll textbox to the hint
/// </summary>
public virtual void DoVisible()
{
Range.tb.DoRangeVisible(Range, true);
Range.tb.Invalidate();
}
private Hint(Range range, Control innerControl, string text, bool inline, bool dock)
{
this.Range = range;
this.Inline = inline;
this.InnerControl = innerControl;
Init();
Dock = dock ? DockStyle.Fill : DockStyle.None;
Text = text;
}
/// <summary>
/// Creates Hint
/// </summary>
/// <param name="range">Linked range</param>
/// <param name="text">Text for simple hint</param>
/// <param name="inline">Inlining. If True then hint will moves apart text</param>
/// <param name="dock">Docking. If True then hint will fill whole line</param>
public Hint(Range range, string text, bool inline, bool dock)
: this(range, null, text, inline, dock)
{
}
/// <summary>
/// Creates Hint
/// </summary>
/// <param name="range">Linked range</param>
/// <param name="text">Text for simple hint</param>
public Hint(Range range, string text)
: this(range, null, text, true, true)
{
}
/// <summary>
/// Creates Hint
/// </summary>
/// <param name="range">Linked range</param>
/// <param name="innerControl">Inner control</param>
/// <param name="inline">Inlining. If True then hint will moves apart text</param>
/// <param name="dock">Docking. If True then hint will fill whole line</param>
public Hint(Range range, Control innerControl, bool inline, bool dock)
: this(range, innerControl, null, inline, dock)
{
}
/// <summary>
/// Creates Hint
/// </summary>
/// <param name="range">Linked range</param>
/// <param name="innerControl">Inner control</param>
public Hint(Range range, Control innerControl)
: this(range, innerControl, null, true, true)
{
}
protected virtual void Init()
{
HostPanel = new UnfocusablePanel();
HostPanel.Click += OnClick;
if (InnerControl != null)
{
HostPanel.Controls.Add(InnerControl);
HostPanel.Width = InnerControl.Width + 2;
HostPanel.Height = InnerControl.Height + 2;
InnerControl.Dock = DockStyle.Fill;
InnerControl.Visible = true;
BackColor = SystemColors.Control;
}
else
{
HostPanel.Height = Range.tb.CharHeight + 5;
}
Cursor = Cursors.Default;
BorderColor = Color.Silver;
BackColor2 = Color.White;
BackColor = InnerControl == null ? Color.Silver : SystemColors.Control;
ForeColor = Color.Black;
TextAlignment = StringAlignment.Near;
Font = Range.tb.Parent == null ? Range.tb.Font : Range.tb.Parent.Font;
}
protected virtual void OnClick(object sender, EventArgs e)
{
Range.tb.OnHintClick(this);
}
}
}

View file

@ -0,0 +1,251 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using KEYS = System.Windows.Forms.Keys;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Dictionary of shortcuts for FCTB
/// </summary>
public class HotkeysMapping : SortedDictionary<Keys, FCTBAction>
{
public virtual void InitDefault()
{
this[KEYS.Control | KEYS.G] = FCTBAction.GoToDialog;
this[KEYS.Control | KEYS.F] = FCTBAction.FindDialog;
this[KEYS.Control | KEYS.Alt | KEYS.F] = FCTBAction.FindChar;
this[KEYS.F3] = FCTBAction.FindNext;
this[KEYS.Control | KEYS.H] = FCTBAction.ReplaceDialog;
this[KEYS.Control | KEYS.C] = FCTBAction.Copy;
this[KEYS.Control | KEYS.Shift | KEYS.C] = FCTBAction.CommentSelected;
this[KEYS.Control | KEYS.X] = FCTBAction.Cut;
this[KEYS.Control | KEYS.V] = FCTBAction.Paste;
this[KEYS.Control | KEYS.A] = FCTBAction.SelectAll;
this[KEYS.Control | KEYS.Z] = FCTBAction.Undo;
this[KEYS.Control | KEYS.R] = FCTBAction.Redo;
this[KEYS.Control | KEYS.U] = FCTBAction.UpperCase;
this[KEYS.Shift | KEYS.Control | KEYS.U] = FCTBAction.LowerCase;
this[KEYS.Control | KEYS.OemMinus] = FCTBAction.NavigateBackward;
this[KEYS.Control | KEYS.Shift | KEYS.OemMinus] = FCTBAction.NavigateForward;
this[KEYS.Control | KEYS.B] = FCTBAction.BookmarkLine;
this[KEYS.Control | KEYS.Shift | KEYS.B] = FCTBAction.UnbookmarkLine;
this[KEYS.Control | KEYS.N] = FCTBAction.GoNextBookmark;
this[KEYS.Control | KEYS.Shift | KEYS.N] = FCTBAction.GoPrevBookmark;
this[KEYS.Alt | KEYS.Back] = FCTBAction.Undo;
this[KEYS.Control | KEYS.Back] = FCTBAction.ClearWordLeft;
this[KEYS.Insert] = FCTBAction.ReplaceMode;
this[KEYS.Control | KEYS.Insert] = FCTBAction.Copy;
this[KEYS.Shift | KEYS.Insert] = FCTBAction.Paste;
this[KEYS.Delete] = FCTBAction.DeleteCharRight;
this[KEYS.Control | KEYS.Delete] = FCTBAction.ClearWordRight;
this[KEYS.Shift | KEYS.Delete] = FCTBAction.Cut;
this[KEYS.Left] = FCTBAction.GoLeft;
this[KEYS.Shift | KEYS.Left] = FCTBAction.GoLeftWithSelection;
this[KEYS.Control | KEYS.Left] = FCTBAction.GoWordLeft;
this[KEYS.Control | KEYS.Shift | KEYS.Left] = FCTBAction.GoWordLeftWithSelection;
this[KEYS.Alt | KEYS.Shift | KEYS.Left] = FCTBAction.GoLeft_ColumnSelectionMode;
this[KEYS.Right] = FCTBAction.GoRight;
this[KEYS.Shift | KEYS.Right] = FCTBAction.GoRightWithSelection;
this[KEYS.Control | KEYS.Right] = FCTBAction.GoWordRight;
this[KEYS.Control | KEYS.Shift | KEYS.Right] = FCTBAction.GoWordRightWithSelection;
this[KEYS.Alt | KEYS.Shift | KEYS.Right] = FCTBAction.GoRight_ColumnSelectionMode;
this[KEYS.Up] = FCTBAction.GoUp;
this[KEYS.Shift | KEYS.Up] = FCTBAction.GoUpWithSelection;
this[KEYS.Alt | KEYS.Shift | KEYS.Up] = FCTBAction.GoUp_ColumnSelectionMode;
this[KEYS.Alt | KEYS.Up] = FCTBAction.MoveSelectedLinesUp;
this[KEYS.Control | KEYS.Up] = FCTBAction.ScrollUp;
this[KEYS.Down] = FCTBAction.GoDown;
this[KEYS.Shift | KEYS.Down] = FCTBAction.GoDownWithSelection;
this[KEYS.Alt | KEYS.Shift | KEYS.Down] = FCTBAction.GoDown_ColumnSelectionMode;
this[KEYS.Alt | KEYS.Down] = FCTBAction.MoveSelectedLinesDown;
this[KEYS.Control | KEYS.Down] = FCTBAction.ScrollDown;
this[KEYS.PageUp] = FCTBAction.GoPageUp;
this[KEYS.Shift | KEYS.PageUp] = FCTBAction.GoPageUpWithSelection;
this[KEYS.PageDown] = FCTBAction.GoPageDown;
this[KEYS.Shift | KEYS.PageDown] = FCTBAction.GoPageDownWithSelection;
this[KEYS.Home] = FCTBAction.GoHome;
this[KEYS.Shift | KEYS.Home] = FCTBAction.GoHomeWithSelection;
this[KEYS.Control | KEYS.Home] = FCTBAction.GoFirstLine;
this[KEYS.Control | KEYS.Shift | KEYS.Home] = FCTBAction.GoFirstLineWithSelection;
this[KEYS.End] = FCTBAction.GoEnd;
this[KEYS.Shift | KEYS.End] = FCTBAction.GoEndWithSelection;
this[KEYS.Control | KEYS.End] = FCTBAction.GoLastLine;
this[KEYS.Control | KEYS.Shift | KEYS.End] = FCTBAction.GoLastLineWithSelection;
this[KEYS.Escape] = FCTBAction.ClearHints;
this[KEYS.Control | KEYS.M] = FCTBAction.MacroRecord;
this[KEYS.Control | KEYS.E] = FCTBAction.MacroExecute;
this[KEYS.Control | KEYS.Space] = FCTBAction.AutocompleteMenu;
this[KEYS.Tab] = FCTBAction.IndentIncrease;
this[KEYS.Shift | KEYS.Tab] = FCTBAction.IndentDecrease;
this[KEYS.Control | KEYS.Subtract] = FCTBAction.ZoomOut;
this[KEYS.Control | KEYS.Add] = FCTBAction.ZoomIn;
this[KEYS.Control | KEYS.D0] = FCTBAction.ZoomNormal;
this[KEYS.Control | KEYS.I] = FCTBAction.AutoIndentChars;
}
public override string ToString()
{
var cult = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
StringBuilder sb = new StringBuilder();
var kc = new KeysConverter();
foreach (var pair in this)
{
sb.AppendFormat("{0}={1}, ", kc.ConvertToString(pair.Key), pair.Value);
}
if (sb.Length > 1)
sb.Remove(sb.Length - 2, 2);
Thread.CurrentThread.CurrentUICulture = cult;
return sb.ToString();
}
public static HotkeysMapping Parse(string s)
{
var result = new HotkeysMapping();
result.Clear();
var cult = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var kc = new KeysConverter();
foreach (var p in s.Split(','))
{
var pp = p.Split('=');
var k = (Keys)kc.ConvertFromString(pp[0].Trim());
var a = (FCTBAction)Enum.Parse(typeof(FCTBAction), pp[1].Trim());
result[k] = a;
}
Thread.CurrentThread.CurrentUICulture = cult;
return result;
}
}
/// <summary>
/// Actions for shortcuts
/// </summary>
public enum FCTBAction
{
None,
AutocompleteMenu,
AutoIndentChars,
BookmarkLine,
ClearHints,
ClearWordLeft,
ClearWordRight,
CommentSelected,
Copy,
Cut,
DeleteCharRight,
FindChar,
FindDialog,
FindNext,
GoDown,
GoDownWithSelection,
GoDown_ColumnSelectionMode,
GoEnd,
GoEndWithSelection,
GoFirstLine,
GoFirstLineWithSelection,
GoHome,
GoHomeWithSelection,
GoLastLine,
GoLastLineWithSelection,
GoLeft,
GoLeftWithSelection,
GoLeft_ColumnSelectionMode,
GoPageDown,
GoPageDownWithSelection,
GoPageUp,
GoPageUpWithSelection,
GoRight,
GoRightWithSelection,
GoRight_ColumnSelectionMode,
GoToDialog,
GoNextBookmark,
GoPrevBookmark,
GoUp,
GoUpWithSelection,
GoUp_ColumnSelectionMode,
GoWordLeft,
GoWordLeftWithSelection,
GoWordRight,
GoWordRightWithSelection,
IndentIncrease,
IndentDecrease,
LowerCase,
MacroExecute,
MacroRecord,
MoveSelectedLinesDown,
MoveSelectedLinesUp,
NavigateBackward,
NavigateForward,
Paste,
Redo,
ReplaceDialog,
ReplaceMode,
ScrollDown,
ScrollUp,
SelectAll,
UnbookmarkLine,
Undo,
UpperCase,
ZoomIn,
ZoomNormal,
ZoomOut,
CustomAction1,
CustomAction2,
CustomAction3,
CustomAction4,
CustomAction5,
CustomAction6,
CustomAction7,
CustomAction8,
CustomAction9,
CustomAction10,
CustomAction11,
CustomAction12,
CustomAction13,
CustomAction14,
CustomAction15,
CustomAction16,
CustomAction17,
CustomAction18,
CustomAction19,
CustomAction20
}
internal class HotkeysEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if ((provider != null) && (((IWindowsFormsEditorService) provider.GetService(typeof(IWindowsFormsEditorService))) != null))
{
var form = new HotkeysEditorForm(HotkeysMapping.Parse(value as string));
if (form.ShowDialog() == DialogResult.OK)
value = form.GetHotkeys().ToString();
}
return value;
}
}
}

View file

@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
public partial class HotkeysEditorForm : Form
{
BindingList<HotkeyWrapper> wrappers = new BindingList<HotkeyWrapper>();
public HotkeysEditorForm(HotkeysMapping hotkeys)
{
InitializeComponent();
BuildWrappers(hotkeys);
dgv.DataSource = wrappers;
}
int CompereKeys(Keys key1, Keys key2)
{
var res = ((int)key1 & 0xff).CompareTo((int)key2 & 0xff);
if (res == 0)
res = key1.CompareTo(key2);
return res;
}
private void BuildWrappers(HotkeysMapping hotkeys)
{
var keys = new List<Keys>(hotkeys.Keys);
keys.Sort(CompereKeys);
wrappers.Clear();
foreach (var k in keys)
wrappers.Add(new HotkeyWrapper(k, hotkeys[k]));
}
/// <summary>
/// Returns edited hotkey map
/// </summary>
/// <returns></returns>
public HotkeysMapping GetHotkeys()
{
var result = new HotkeysMapping();
foreach (var w in wrappers)
result[w.ToKeyData()] = w.Action;
return result;
}
private void btAdd_Click(object sender, EventArgs e)
{
wrappers.Add(new HotkeyWrapper(Keys.None, FCTBAction.None));
}
private void dgv_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
var cell = (dgv[0, e.RowIndex] as DataGridViewComboBoxCell);
if(cell.Items.Count == 0)
foreach(var item in new string[]{"", "Ctrl", "Ctrl + Shift", "Ctrl + Alt", "Shift", "Shift + Alt", "Alt", "Ctrl + Shift + Alt"})
cell.Items.Add(item);
cell = (dgv[1, e.RowIndex] as DataGridViewComboBoxCell);
if (cell.Items.Count == 0)
foreach (var item in Enum.GetValues(typeof(Keys)))
cell.Items.Add(item);
cell = (dgv[2, e.RowIndex] as DataGridViewComboBoxCell);
if (cell.Items.Count == 0)
foreach (var item in Enum.GetValues(typeof(FCTBAction)))
cell.Items.Add(item);
}
private void btResore_Click(object sender, EventArgs e)
{
HotkeysMapping h = new HotkeysMapping();
h.InitDefault();
BuildWrappers(h);
}
private void btRemove_Click(object sender, EventArgs e)
{
for (int i = dgv.RowCount - 1; i >= 0; i--)
if (dgv.Rows[i].Selected) dgv.Rows.RemoveAt(i);
}
private void HotkeysEditorForm_FormClosing(object sender, FormClosingEventArgs e)
{
if(DialogResult == System.Windows.Forms.DialogResult.OK)
{
var actions = GetUnAssignedActions();
if (!string.IsNullOrEmpty(actions))
{
if (MessageBox.Show("Some actions are not assigned!\r\nActions: " + actions + "\r\nPress Yes to save and exit, press No to continue editing", "Some actions is not assigned", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.No)
e.Cancel = true;
}
}
}
private string GetUnAssignedActions()
{
StringBuilder sb = new StringBuilder();
var dic = new Dictionary<FCTBAction, FCTBAction>();
foreach (var w in wrappers)
dic[w.Action] = w.Action;
foreach (var item in Enum.GetValues(typeof(FCTBAction)))
if ((FCTBAction)item != FCTBAction.None)
if(!((FCTBAction)item).ToString().StartsWith("CustomAction"))
{
if(!dic.ContainsKey((FCTBAction)item))
sb.Append(item+", ");
}
return sb.ToString().TrimEnd(' ', ',');
}
}
internal class HotkeyWrapper
{
public HotkeyWrapper(Keys keyData, FCTBAction action)
{
KeyEventArgs a = new KeyEventArgs(keyData);
Ctrl = a.Control;
Shift = a.Shift;
Alt = a.Alt;
Key = a.KeyCode;
Action = action;
}
public Keys ToKeyData()
{
var res = Key;
if (Ctrl) res |= Keys.Control;
if (Alt) res |= Keys.Alt;
if (Shift) res |= Keys.Shift;
return res;
}
bool Ctrl;
bool Shift;
bool Alt;
public string Modifiers
{
get
{
var res = "";
if (Ctrl) res += "Ctrl + ";
if (Shift) res += "Shift + ";
if (Alt) res += "Alt + ";
return res.Trim(' ', '+');
}
set
{
if (value == null)
{
Ctrl = Alt = Shift = false;
}
else
{
Ctrl = value.Contains("Ctrl");
Shift = value.Contains("Shift");
Alt = value.Contains("Alt");
}
}
}
public Keys Key { get; set; }
public FCTBAction Action { get; set; }
}
}

View file

@ -0,0 +1,210 @@
namespace FastColoredTextBoxNS
{
partial class HotkeysEditorForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.dgv = new System.Windows.Forms.DataGridView();
this.cbModifiers = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.cbKey = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.cbAction = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.btAdd = new System.Windows.Forms.Button();
this.btRemove = new System.Windows.Forms.Button();
this.btCancel = new System.Windows.Forms.Button();
this.btOk = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.btResore = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dgv)).BeginInit();
this.SuspendLayout();
//
// dgv
//
this.dgv.AllowUserToAddRows = false;
this.dgv.AllowUserToDeleteRows = false;
this.dgv.AllowUserToResizeColumns = false;
this.dgv.AllowUserToResizeRows = false;
this.dgv.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dgv.BackgroundColor = System.Drawing.SystemColors.Control;
this.dgv.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgv.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.cbModifiers,
this.cbKey,
this.cbAction});
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.LightSteelBlue;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dgv.DefaultCellStyle = dataGridViewCellStyle1;
this.dgv.GridColor = System.Drawing.SystemColors.Control;
this.dgv.Location = new System.Drawing.Point(12, 28);
this.dgv.Name = "dgv";
this.dgv.RowHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.None;
this.dgv.RowHeadersVisible = false;
this.dgv.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing;
this.dgv.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgv.Size = new System.Drawing.Size(525, 278);
this.dgv.TabIndex = 0;
this.dgv.RowsAdded += new System.Windows.Forms.DataGridViewRowsAddedEventHandler(this.dgv_RowsAdded);
//
// cbModifiers
//
this.cbModifiers.DataPropertyName = "Modifiers";
this.cbModifiers.DisplayStyle = System.Windows.Forms.DataGridViewComboBoxDisplayStyle.ComboBox;
this.cbModifiers.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cbModifiers.HeaderText = "Modifiers";
this.cbModifiers.Name = "cbModifiers";
//
// cbKey
//
this.cbKey.DataPropertyName = "Key";
this.cbKey.DisplayStyle = System.Windows.Forms.DataGridViewComboBoxDisplayStyle.ComboBox;
this.cbKey.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cbKey.HeaderText = "Key";
this.cbKey.Name = "cbKey";
this.cbKey.Resizable = System.Windows.Forms.DataGridViewTriState.True;
this.cbKey.Width = 120;
//
// cbAction
//
this.cbAction.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.cbAction.DataPropertyName = "Action";
this.cbAction.DisplayStyle = System.Windows.Forms.DataGridViewComboBoxDisplayStyle.ComboBox;
this.cbAction.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cbAction.HeaderText = "Action";
this.cbAction.Name = "cbAction";
//
// btAdd
//
this.btAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btAdd.Location = new System.Drawing.Point(13, 322);
this.btAdd.Name = "btAdd";
this.btAdd.Size = new System.Drawing.Size(75, 23);
this.btAdd.TabIndex = 1;
this.btAdd.Text = "Add";
this.btAdd.UseVisualStyleBackColor = true;
this.btAdd.Click += new System.EventHandler(this.btAdd_Click);
//
// btRemove
//
this.btRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btRemove.Location = new System.Drawing.Point(103, 322);
this.btRemove.Name = "btRemove";
this.btRemove.Size = new System.Drawing.Size(75, 23);
this.btRemove.TabIndex = 2;
this.btRemove.Text = "Remove";
this.btRemove.UseVisualStyleBackColor = true;
this.btRemove.Click += new System.EventHandler(this.btRemove_Click);
//
// btCancel
//
this.btCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btCancel.Location = new System.Drawing.Point(460, 322);
this.btCancel.Name = "btCancel";
this.btCancel.Size = new System.Drawing.Size(75, 23);
this.btCancel.TabIndex = 4;
this.btCancel.Text = "Cancel";
this.btCancel.UseVisualStyleBackColor = true;
//
// btOk
//
this.btOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btOk.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btOk.Location = new System.Drawing.Point(379, 322);
this.btOk.Name = "btOk";
this.btOk.Size = new System.Drawing.Size(75, 23);
this.btOk.TabIndex = 3;
this.btOk.Text = "OK";
this.btOk.UseVisualStyleBackColor = true;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.label1.Location = new System.Drawing.Point(12, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(114, 16);
this.label1.TabIndex = 5;
this.label1.Text = "Hotkeys mapping";
//
// btResore
//
this.btResore.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.btResore.Location = new System.Drawing.Point(194, 322);
this.btResore.Name = "btResore";
this.btResore.Size = new System.Drawing.Size(105, 23);
this.btResore.TabIndex = 6;
this.btResore.Text = "Restore default";
this.btResore.UseVisualStyleBackColor = true;
this.btResore.Click += new System.EventHandler(this.btResore_Click);
//
// HotkeysEditorForm
//
this.AcceptButton = this.btOk;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btCancel;
this.ClientSize = new System.Drawing.Size(549, 357);
this.Controls.Add(this.btResore);
this.Controls.Add(this.label1);
this.Controls.Add(this.btCancel);
this.Controls.Add(this.btOk);
this.Controls.Add(this.btRemove);
this.Controls.Add(this.btAdd);
this.Controls.Add(this.dgv);
this.MaximumSize = new System.Drawing.Size(565, 700);
this.MinimumSize = new System.Drawing.Size(565, 395);
this.Name = "HotkeysEditorForm";
this.ShowIcon = false;
this.Text = "Hotkeys Editor";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.HotkeysEditorForm_FormClosing);
((System.ComponentModel.ISupportInitialize)(this.dgv)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.DataGridView dgv;
private System.Windows.Forms.Button btAdd;
private System.Windows.Forms.Button btRemove;
private System.Windows.Forms.Button btCancel;
private System.Windows.Forms.Button btOk;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btResore;
private System.Windows.Forms.DataGridViewComboBoxColumn cbModifiers;
private System.Windows.Forms.DataGridViewComboBoxColumn cbKey;
private System.Windows.Forms.DataGridViewComboBoxColumn cbAction;
}
}

View file

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="cbModifiers.UserAddedColumn" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="cbKey.UserAddedColumn" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="cbAction.UserAddedColumn" type="System.Boolean, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

View file

@ -0,0 +1,105 @@
using System;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Limited stack
/// </summary>
public class LimitedStack<T>
{
T[] items;
int count;
int start;
/// <summary>
/// Max stack length
/// </summary>
public int MaxItemCount
{
get { return items.Length; }
}
/// <summary>
/// Current length of stack
/// </summary>
public int Count
{
get { return count; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="maxItemCount">Maximum length of stack</param>
public LimitedStack(int maxItemCount)
{
items = new T[maxItemCount];
count = 0;
start = 0;
}
/// <summary>
/// Pop item
/// </summary>
public T Pop()
{
if (count == 0)
throw new Exception("Stack is empty");
int i = LastIndex;
T item = items[i];
items[i] = default(T);
count--;
return item;
}
int LastIndex
{
get { return (start + count - 1) % items.Length; }
}
/// <summary>
/// Peek item
/// </summary>
public T Peek()
{
if (count == 0)
return default(T);
return items[LastIndex];
}
/// <summary>
/// Push item
/// </summary>
public void Push(T item)
{
if (count == items.Length)
start = (start + 1) % items.Length;
else
count++;
items[LastIndex] = item;
}
/// <summary>
/// Clear stack
/// </summary>
public void Clear()
{
items = new T[items.Length];
count = 0;
start = 0;
}
public T[] ToArray()
{
T[] result = new T[count];
for (int i = 0; i < count; i++)
result[i] = items[(start + i) % items.Length];
return result;
}
}
}

View file

@ -0,0 +1,289 @@
using System.Collections.Generic;
using System;
using System.Text;
using System.Drawing;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Line of text
/// </summary>
public class Line : IList<Char>
{
protected List<Char> chars;
public string FoldingStartMarker { get; set; }
public string FoldingEndMarker { get; set; }
/// <summary>
/// Text of line was changed
/// </summary>
public bool IsChanged { get; set; }
/// <summary>
/// Time of last visit of caret in this line
/// </summary>
/// <remarks>This property can be used for forward/backward navigating</remarks>
public DateTime LastVisit { get; set; }
/// <summary>
/// Background brush.
/// </summary>
public Brush BackgroundBrush { get; set;}
/// <summary>
/// Unique ID
/// </summary>
public int UniqueId { get; private set; }
/// <summary>
/// Count of needed start spaces for AutoIndent
/// </summary>
public int AutoIndentSpacesNeededCount
{
get;
set;
}
internal Line(int uid)
{
this.UniqueId = uid;
chars = new List<Char>();
}
/// <summary>
/// Clears style of chars, delete folding markers
/// </summary>
public void ClearStyle(StyleIndex styleIndex)
{
FoldingStartMarker = null;
FoldingEndMarker = null;
for (int i = 0; i < Count; i++)
{
Char c = this[i];
c.style &= ~styleIndex;
this[i] = c;
}
}
/// <summary>
/// Text of the line
/// </summary>
public virtual string Text
{
get{
StringBuilder sb = new StringBuilder(Count);
foreach(Char c in this)
sb.Append(c.c);
return sb.ToString();
}
}
/// <summary>
/// Clears folding markers
/// </summary>
public void ClearFoldingMarkers()
{
FoldingStartMarker = null;
FoldingEndMarker = null;
}
/// <summary>
/// Count of start spaces
/// </summary>
public int StartSpacesCount
{
get
{
int spacesCount = 0;
for (int i = 0; i < Count; i++)
if (this[i].c == ' ')
spacesCount++;
else
break;
return spacesCount;
}
}
public int IndexOf(Char item)
{
return chars.IndexOf(item);
}
public void Insert(int index, Char item)
{
chars.Insert(index, item);
}
public void RemoveAt(int index)
{
chars.RemoveAt(index);
}
public Char this[int index]
{
get
{
return chars[index];
}
set
{
chars[index] = value;
}
}
public void Add(Char item)
{
chars.Add(item);
}
public void Clear()
{
chars.Clear();
}
public bool Contains(Char item)
{
return chars.Contains(item);
}
public void CopyTo(Char[] array, int arrayIndex)
{
chars.CopyTo(array, arrayIndex);
}
/// <summary>
/// Chars count
/// </summary>
public int Count
{
get { return chars.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(Char item)
{
return chars.Remove(item);
}
public IEnumerator<Char> GetEnumerator()
{
return chars.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return chars.GetEnumerator() as System.Collections.IEnumerator;
}
public virtual void RemoveRange(int index, int count)
{
if (index >= Count)
return;
chars.RemoveRange(index, Math.Min(Count - index, count));
}
public virtual void TrimExcess()
{
chars.TrimExcess();
}
public virtual void AddRange(IEnumerable<Char> collection)
{
chars.AddRange(collection);
}
}
public struct LineInfo
{
List<int> cutOffPositions;
//Y coordinate of line on screen
internal int startY;
internal int bottomPadding;
//indent of secondary wordwrap strings (in chars)
internal int wordWrapIndent;
/// <summary>
/// Visible state
/// </summary>
public VisibleState VisibleState;
public LineInfo(int startY)
{
cutOffPositions = null;
VisibleState = VisibleState.Visible;
this.startY = startY;
bottomPadding = 0;
wordWrapIndent = 0;
}
/// <summary>
/// Positions for wordwrap cutoffs
/// </summary>
public List<int> CutOffPositions
{
get
{
if (cutOffPositions == null)
cutOffPositions = new List<int>();
return cutOffPositions;
}
}
/// <summary>
/// Count of wordwrap string count for this line
/// </summary>
public int WordWrapStringsCount
{
get
{
switch (VisibleState)
{
case VisibleState.Visible:
if (cutOffPositions == null)
return 1;
else
return cutOffPositions.Count + 1;
case VisibleState.Hidden: return 0;
case VisibleState.StartOfHiddenBlock: return 1;
}
return 0;
}
}
internal int GetWordWrapStringStartPosition(int iWordWrapLine)
{
return iWordWrapLine == 0 ? 0 : CutOffPositions[iWordWrapLine - 1];
}
internal int GetWordWrapStringFinishPosition(int iWordWrapLine, Line line)
{
if (WordWrapStringsCount <= 0)
return 0;
return iWordWrapLine == WordWrapStringsCount - 1 ? line.Count - 1 : CutOffPositions[iWordWrapLine] - 1;
}
/// <summary>
/// Gets index of wordwrap string for given char position
/// </summary>
public int GetWordWrapStringIndex(int iChar)
{
if (cutOffPositions == null || cutOffPositions.Count == 0) return 0;
for (int i = 0; i < cutOffPositions.Count; i++)
if (cutOffPositions[i] >/*>=*/ iChar)
return i;
return cutOffPositions.Count;
}
}
public enum VisibleState: byte
{
Visible, StartOfHiddenBlock, Hidden
}
public enum IndentMarker
{
None,
Increased,
Decreased
}
}

View file

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FastColoredTextBoxNS
{
public class LinesAccessor : IList<string>
{
IList<Line> ts;
public LinesAccessor(IList<Line> ts)
{
this.ts = ts;
}
public int IndexOf(string item)
{
for (int i = 0; i < ts.Count; i++)
if (ts[i].Text == item)
return i;
return -1;
}
public void Insert(int index, string item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
public string this[int index]
{
get
{
return ts[index].Text;
}
set
{
throw new NotImplementedException();
}
}
public void Add(string item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(string item)
{
for (int i = 0; i < ts.Count; i++)
if (ts[i].Text == item)
return true;
return false;
}
public void CopyTo(string[] array, int arrayIndex)
{
for (int i = 0; i < ts.Count; i++)
array[i + arrayIndex] = ts[i].Text;
}
public int Count
{
get { return ts.Count; }
}
public bool IsReadOnly
{
get { return true; }
}
public bool Remove(string item)
{
throw new NotImplementedException();
}
public IEnumerator<string> GetEnumerator()
{
for (int i = 0; i < ts.Count; i++)
yield return ts[i].Text;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -0,0 +1,183 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;
namespace FastColoredTextBoxNS
{
/// <summary>
/// This class records, stores and executes the macros.
/// </summary>
public class MacrosManager
{
private readonly List<object> macro = new List<object>();
internal MacrosManager(FastColoredTextBox ctrl)
{
UnderlayingControl = ctrl;
AllowMacroRecordingByUser = true;
}
/// <summary>
/// Allows to user to record macros
/// </summary>
public bool AllowMacroRecordingByUser { get;set; }
private bool isRecording;
/// <summary>
/// Returns current recording state. Set to True/False to start/stop recording programmatically.
/// </summary>
public bool IsRecording
{
get { return isRecording; }
set { isRecording = value; UnderlayingControl.Invalidate(); }
}
/// <summary>
/// FCTB
/// </summary>
public FastColoredTextBox UnderlayingControl { get; private set; }
/// <summary>
/// Executes recorded macro
/// </summary>
/// <returns></returns>
public void ExecuteMacros()
{
IsRecording = false;
UnderlayingControl.BeginUpdate();
UnderlayingControl.Selection.BeginUpdate();
UnderlayingControl.BeginAutoUndo();
foreach (var item in macro)
{
if (item is Keys)
{
UnderlayingControl.ProcessKey((Keys)item);
}
if (item is KeyValuePair<char, Keys>)
{
var p = (KeyValuePair<char, Keys>)item;
UnderlayingControl.ProcessKey(p.Key, p.Value);
}
}
UnderlayingControl.EndAutoUndo();
UnderlayingControl.Selection.EndUpdate();
UnderlayingControl.EndUpdate();
}
/// <summary>
/// Adds the char to current macro
/// </summary>
public void AddCharToMacros(char c, Keys modifiers)
{
macro.Add(new KeyValuePair<char, Keys>(c, modifiers));
}
/// <summary>
/// Adds keyboard key to current macro
/// </summary>
public void AddKeyToMacros(Keys keyData)
{
macro.Add(keyData);
}
/// <summary>
/// Clears last recorded macro
/// </summary>
public void ClearMacros()
{
macro.Clear();
}
internal void ProcessKey(Keys keyData)
{
if (IsRecording)
AddKeyToMacros(keyData);
}
internal void ProcessKey(char c, Keys modifiers)
{
if (IsRecording)
AddCharToMacros(c, modifiers);
}
/// <summary>
/// Returns True if last macro is empty
/// </summary>
public bool MacroIsEmpty { get { return macro.Count == 0; }}
/// <summary>
/// Macros as string.
/// </summary>
public string Macros
{
get
{
var cult = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var kc = new KeysConverter();
StringBuilder sb = new StringBuilder();
sb.AppendLine("<macros>");
foreach (var item in macro)
{
if (item is Keys)
{
sb.AppendFormat("<item key='{0}' />\r\n", kc.ConvertToString((Keys)item));
}
else if (item is KeyValuePair<char, Keys>)
{
var p = (KeyValuePair<char, Keys>)item;
sb.AppendFormat("<item char='{0}' key='{1}' />\r\n", (int)p.Key, kc.ConvertToString(p.Value));
}
}
sb.AppendLine("</macros>");
Thread.CurrentThread.CurrentUICulture = cult;
return sb.ToString();
}
set
{
isRecording = false;
ClearMacros();
if (string.IsNullOrEmpty(value))
return;
var doc = new XmlDocument();
doc.LoadXml(value);
var list = doc.SelectNodes("./macros/item");
var cult = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var kc = new KeysConverter();
if(list != null)
foreach (XmlElement node in list)
{
var ca = node.GetAttributeNode("char");
var ka = node.GetAttributeNode("key");
if (ca != null)
{
if(ka!=null)
AddCharToMacros((char)int.Parse(ca.Value), (Keys)kc.ConvertFromString(ka.Value));
else
AddCharToMacros((char)int.Parse(ca.Value), Keys.None);
}else
if(ka!=null)
AddKeyToMacros((Keys)kc.ConvertFromString(ka.Value));
}
Thread.CurrentThread.CurrentUICulture = cult;
}
}
}
}

View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FastColoredTextBoxNS
{
internal class MonoUtility
{
// .Net 1.0 and 1.1 didn't have the PlatformID value for Unix, so Mono used the value 128.
private const PlatformID MonoUnix = (PlatformID)128;
public static bool IsLinux
{
get
{
PlatformID p = Environment.OSVersion.Platform;
return (p == PlatformID.Unix) || (p == PlatformID.MacOSX) || (p == MonoUnix);
}
}
}
}

View file

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace FastColoredTextBoxNS
{
internal class NativeMethods
{
//[DllImport("user32.dll")]
//internal static extern IntPtr GetOpenClipboardWindow(); // unused ?
[DllImport("user32.dll")]
protected static extern bool CloseClipboard(); // corrected intptr -> bool
[DllImport("Imm32.dll")]
protected static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("Imm32.dll")]
protected static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("User32.dll")]
protected static extern bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight);
[DllImport("User32.dll")]
protected static extern bool SetCaretPos(int x, int y);
//[DllImport("User32.dll")]
//internal static extern bool DestroyCaret(); // unused ?
[DllImport("User32.dll")]
protected static extern bool ShowCaret(IntPtr hWnd);
[DllImport("User32.dll")]
protected static extern bool HideCaret(IntPtr hWnd);
[DllImport("User32.dll")]
protected static extern IntPtr SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); // change corrent
[DllImport("kernel32.dll")]
protected static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
[DllImport("kernel32.dll")]
protected static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_INFO
{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public UIntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
};
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FastColoredTextBoxNS
{
internal class NativeMethodsWrapper : NativeMethods
{
internal new static bool CloseClipboard()
{
if (MonoUtility.IsLinux)
{
return true; // TODO
}
else
{
return NativeMethods.CloseClipboard();
}
}
internal new static IntPtr ImmGetContext(IntPtr hWnd)
{
if (MonoUtility.IsLinux)
{
return IntPtr.Zero;
}
else
{
return NativeMethods.ImmGetContext(hWnd);
}
}
internal new static IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC)
{
if (MonoUtility.IsLinux)
{
return IntPtr.Zero;
}
else
{
return NativeMethods.ImmAssociateContext(hWnd, hIMC);
}
}
internal new static bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight)
{
if (MonoUtility.IsLinux)
{
return true; // TODO
}
else
{
return NativeMethods.CreateCaret(hWnd, hBitmap, nWidth, nHeight);
}
}
internal new static bool SetCaretPos(int x, int y)
{
if (MonoUtility.IsLinux)
{
return true; // TODO
}
else
{
return NativeMethods.SetCaretPos(x, y);
}
}
internal new static bool ShowCaret(IntPtr hWnd)
{
if (MonoUtility.IsLinux)
{
return true; // TODO
}
else
{
return NativeMethods.ShowCaret(hWnd);
}
}
internal new static bool HideCaret(IntPtr hWnd)
{
if (MonoUtility.IsLinux)
{
return true; // TODO
}
else
{
return NativeMethods.HideCaret(hWnd);
}
}
internal new static IntPtr SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam)
{
if (MonoUtility.IsLinux)
{
return IntPtr.Zero; // TODO
}
else
{
return NativeMethods.SendMessage(hWnd, wMsg, wParam, lParam);
}
}
internal new static void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo)
{
if (MonoUtility.IsLinux)
{
throw new ApplicationException("This method is not supported in mono");
}
else
{
NativeMethods.GetNativeSystemInfo(ref lpSystemInfo);
}
}
internal new static void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo)
{
if (MonoUtility.IsLinux)
{
throw new ApplicationException("This method is not supported in mono");
}
else
{
NativeMethods.GetSystemInfo(ref lpSystemInfo);
}
}
}
}

View file

@ -0,0 +1,99 @@
using System;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Line index and char index
/// </summary>
public struct Place : IEquatable<Place>
{
public int iChar;
public int iLine;
public Place(int iChar, int iLine)
{
this.iChar = iChar;
this.iLine = iLine;
}
public void Offset(int dx, int dy)
{
iChar += dx;
iLine += dy;
}
public bool Equals(Place other)
{
return iChar == other.iChar && iLine == other.iLine;
}
public override bool Equals(object obj)
{
return (obj is Place) && Equals((Place)obj);
}
public override int GetHashCode()
{
return iChar.GetHashCode() ^ iLine.GetHashCode();
}
public static bool operator !=(Place p1, Place p2)
{
return !p1.Equals(p2);
}
public static bool operator ==(Place p1, Place p2)
{
return p1.Equals(p2);
}
public static bool operator <(Place p1, Place p2)
{
if (p1.iLine < p2.iLine) return true;
if (p1.iLine > p2.iLine) return false;
if (p1.iChar < p2.iChar) return true;
return false;
}
public static bool operator <=(Place p1, Place p2)
{
if (p1.Equals(p2)) return true;
if (p1.iLine < p2.iLine) return true;
if (p1.iLine > p2.iLine) return false;
if (p1.iChar < p2.iChar) return true;
return false;
}
public static bool operator >(Place p1, Place p2)
{
if (p1.iLine > p2.iLine) return true;
if (p1.iLine < p2.iLine) return false;
if (p1.iChar > p2.iChar) return true;
return false;
}
public static bool operator >=(Place p1, Place p2)
{
if (p1.Equals(p2)) return true;
if (p1.iLine > p2.iLine) return true;
if (p1.iLine < p2.iLine) return false;
if (p1.iChar > p2.iChar) return true;
return false;
}
public static Place operator +(Place p1, Place p2)
{
return new Place(p1.iChar + p2.iChar, p1.iLine + p2.iLine);
}
public static Place Empty
{
get { return new Place(); }
}
public override string ToString()
{
return "(" + iChar + "," + iLine + ")";
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace FastColoredTextBoxNS
{
public static class PlatformType
{
const ushort PROCESSOR_ARCHITECTURE_INTEL = 0;
const ushort PROCESSOR_ARCHITECTURE_IA64 = 6;
const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9;
const ushort PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF;
public static Platform GetOperationSystemPlatform()
{
if(MonoUtility.IsLinux)
{
return Platform.Unknown;
}
else
{
var sysInfo = new NativeMethods.SYSTEM_INFO();
// WinXP and older - use GetNativeSystemInfo
if (Environment.OSVersion.Version.Major > 5 ||
(Environment.OSVersion.Version.Major == 5 && Environment.OSVersion.Version.Minor >= 1))
{
NativeMethodsWrapper.GetNativeSystemInfo(ref sysInfo);
}
// else use GetSystemInfo
else
{
NativeMethodsWrapper.GetSystemInfo(ref sysInfo);
}
switch (sysInfo.wProcessorArchitecture)
{
case PROCESSOR_ARCHITECTURE_IA64:
case PROCESSOR_ARCHITECTURE_AMD64:
return Platform.X64;
case PROCESSOR_ARCHITECTURE_INTEL:
return Platform.X86;
default:
return Platform.Unknown;
}
}
}
}
public enum Platform
{
X86,
X64,
Unknown
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,187 @@
using System;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
public partial class ReplaceForm : Form
{
FastColoredTextBox tb;
bool firstSearch = true;
Place startPlace;
public ReplaceForm(FastColoredTextBox tb)
{
InitializeComponent();
this.tb = tb;
}
private void btClose_Click(object sender, EventArgs e)
{
Close();
}
private void btFindNext_Click(object sender, EventArgs e)
{
try
{
if (!Find(tbFind.Text))
MessageBox.Show("Not found");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public List<Range> FindAll(string pattern)
{
var opt = cbMatchCase.Checked ? RegexOptions.None : RegexOptions.IgnoreCase;
if (!cbRegex.Checked)
pattern = Regex.Escape(pattern);
if (cbWholeWord.Checked)
pattern = "\\b" + pattern + "\\b";
//
var range = tb.Selection.IsEmpty? tb.Range.Clone() : tb.Selection.Clone();
//
var list = new List<Range>();
foreach (var r in range.GetRangesByLines(pattern, opt))
list.Add(r);
return list;
}
public bool Find(string pattern)
{
RegexOptions opt = cbMatchCase.Checked ? RegexOptions.None : RegexOptions.IgnoreCase;
if (!cbRegex.Checked)
pattern = Regex.Escape(pattern);
if (cbWholeWord.Checked)
pattern = "\\b" + pattern + "\\b";
//
Range range = tb.Selection.Clone();
range.Normalize();
//
if (firstSearch)
{
startPlace = range.Start;
firstSearch = false;
}
//
range.Start = range.End;
if (range.Start >= startPlace)
range.End = new Place(tb.GetLineLength(tb.LinesCount - 1), tb.LinesCount - 1);
else
range.End = startPlace;
//
foreach (var r in range.GetRangesByLines(pattern, opt))
{
tb.Selection.Start = r.Start;
tb.Selection.End = r.End;
tb.DoSelectionVisible();
tb.Invalidate();
return true;
}
if (range.Start >= startPlace && startPlace > Place.Empty)
{
tb.Selection.Start = new Place(0, 0);
return Find(pattern);
}
return false;
}
private void tbFind_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r')
btFindNext_Click(sender, null);
if (e.KeyChar == '\x1b')
Hide();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) // David
{
if (keyData == Keys.Escape)
{
this.Close();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
private void ReplaceForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
this.tb.Focus();
}
private void btReplace_Click(object sender, EventArgs e)
{
try
{
if (tb.SelectionLength != 0)
if (!tb.Selection.ReadOnly)
tb.InsertText(tbReplace.Text);
btFindNext_Click(sender, null);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btReplaceAll_Click(object sender, EventArgs e)
{
try
{
tb.Selection.BeginUpdate();
//search
var ranges = FindAll(tbFind.Text);
//check readonly
var ro = false;
foreach (var r in ranges)
if (r.ReadOnly)
{
ro = true;
break;
}
//replace
if (!ro)
if (ranges.Count > 0)
{
tb.TextSource.Manager.ExecuteCommand(new ReplaceTextCommand(tb.TextSource, ranges, tbReplace.Text));
tb.Selection.Start = new Place(0, 0);
}
//
tb.Invalidate();
MessageBox.Show(ranges.Count + " occurrence(s) replaced");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
tb.Selection.EndUpdate();
}
protected override void OnActivated(EventArgs e)
{
tbFind.Focus();
ResetSerach();
}
void ResetSerach()
{
firstSearch = true;
}
private void cbMatchCase_CheckedChanged(object sender, EventArgs e)
{
ResetSerach();
}
}
}

View file

@ -0,0 +1,196 @@
namespace FastColoredTextBoxNS
{
partial class ReplaceForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btClose = new System.Windows.Forms.Button();
this.btFindNext = new System.Windows.Forms.Button();
this.tbFind = new System.Windows.Forms.TextBox();
this.cbRegex = new System.Windows.Forms.CheckBox();
this.cbMatchCase = new System.Windows.Forms.CheckBox();
this.label1 = new System.Windows.Forms.Label();
this.cbWholeWord = new System.Windows.Forms.CheckBox();
this.btReplace = new System.Windows.Forms.Button();
this.btReplaceAll = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.tbReplace = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// btClose
//
this.btClose.Location = new System.Drawing.Point(273, 153);
this.btClose.Name = "btClose";
this.btClose.Size = new System.Drawing.Size(75, 23);
this.btClose.TabIndex = 8;
this.btClose.Text = "Close";
this.btClose.UseVisualStyleBackColor = true;
this.btClose.Click += new System.EventHandler(this.btClose_Click);
//
// btFindNext
//
this.btFindNext.Location = new System.Drawing.Point(111, 124);
this.btFindNext.Name = "btFindNext";
this.btFindNext.Size = new System.Drawing.Size(75, 23);
this.btFindNext.TabIndex = 5;
this.btFindNext.Text = "Find next";
this.btFindNext.UseVisualStyleBackColor = true;
this.btFindNext.Click += new System.EventHandler(this.btFindNext_Click);
//
// tbFind
//
this.tbFind.Location = new System.Drawing.Point(62, 12);
this.tbFind.Name = "tbFind";
this.tbFind.Size = new System.Drawing.Size(286, 20);
this.tbFind.TabIndex = 0;
this.tbFind.TextChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
this.tbFind.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tbFind_KeyPress);
//
// cbRegex
//
this.cbRegex.AutoSize = true;
this.cbRegex.Location = new System.Drawing.Point(273, 38);
this.cbRegex.Name = "cbRegex";
this.cbRegex.Size = new System.Drawing.Size(57, 17);
this.cbRegex.TabIndex = 3;
this.cbRegex.Text = "Regex";
this.cbRegex.UseVisualStyleBackColor = true;
this.cbRegex.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// cbMatchCase
//
this.cbMatchCase.AutoSize = true;
this.cbMatchCase.Location = new System.Drawing.Point(66, 38);
this.cbMatchCase.Name = "cbMatchCase";
this.cbMatchCase.Size = new System.Drawing.Size(82, 17);
this.cbMatchCase.TabIndex = 1;
this.cbMatchCase.Text = "Match case";
this.cbMatchCase.UseVisualStyleBackColor = true;
this.cbMatchCase.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(23, 14);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(33, 13);
this.label1.TabIndex = 5;
this.label1.Text = "Find: ";
//
// cbWholeWord
//
this.cbWholeWord.AutoSize = true;
this.cbWholeWord.Location = new System.Drawing.Point(154, 38);
this.cbWholeWord.Name = "cbWholeWord";
this.cbWholeWord.Size = new System.Drawing.Size(113, 17);
this.cbWholeWord.TabIndex = 2;
this.cbWholeWord.Text = "Match whole word";
this.cbWholeWord.UseVisualStyleBackColor = true;
this.cbWholeWord.CheckedChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
//
// btReplace
//
this.btReplace.Location = new System.Drawing.Point(192, 124);
this.btReplace.Name = "btReplace";
this.btReplace.Size = new System.Drawing.Size(75, 23);
this.btReplace.TabIndex = 6;
this.btReplace.Text = "Replace";
this.btReplace.UseVisualStyleBackColor = true;
this.btReplace.Click += new System.EventHandler(this.btReplace_Click);
//
// btReplaceAll
//
this.btReplaceAll.Location = new System.Drawing.Point(273, 124);
this.btReplaceAll.Name = "btReplaceAll";
this.btReplaceAll.Size = new System.Drawing.Size(75, 23);
this.btReplaceAll.TabIndex = 7;
this.btReplaceAll.Text = "Replace all";
this.btReplaceAll.UseVisualStyleBackColor = true;
this.btReplaceAll.Click += new System.EventHandler(this.btReplaceAll_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(6, 81);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(50, 13);
this.label2.TabIndex = 9;
this.label2.Text = "Replace:";
//
// tbReplace
//
this.tbReplace.Location = new System.Drawing.Point(62, 78);
this.tbReplace.Name = "tbReplace";
this.tbReplace.Size = new System.Drawing.Size(286, 20);
this.tbReplace.TabIndex = 0;
this.tbReplace.TextChanged += new System.EventHandler(this.cbMatchCase_CheckedChanged);
this.tbReplace.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.tbFind_KeyPress);
//
// ReplaceForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(360, 191);
this.Controls.Add(this.tbFind);
this.Controls.Add(this.label2);
this.Controls.Add(this.tbReplace);
this.Controls.Add(this.btReplaceAll);
this.Controls.Add(this.btReplace);
this.Controls.Add(this.cbWholeWord);
this.Controls.Add(this.label1);
this.Controls.Add(this.cbMatchCase);
this.Controls.Add(this.cbRegex);
this.Controls.Add(this.btFindNext);
this.Controls.Add(this.btClose);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "ReplaceForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Find and replace";
this.TopMost = true;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ReplaceForm_FormClosing);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btClose;
private System.Windows.Forms.Button btFindNext;
private System.Windows.Forms.CheckBox cbRegex;
private System.Windows.Forms.CheckBox cbMatchCase;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox cbWholeWord;
private System.Windows.Forms.Button btReplace;
private System.Windows.Forms.Button btReplaceAll;
private System.Windows.Forms.Label label2;
public System.Windows.Forms.TextBox tbFind;
public System.Windows.Forms.TextBox tbReplace;
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
public partial class Ruler : UserControl
{
public EventHandler TargetChanged;
[DefaultValue(typeof(Color), "ControlLight")]
public Color BackColor2 { get; set; }
[DefaultValue(typeof(Color), "DarkGray")]
public Color TickColor { get; set; }
[DefaultValue(typeof(Color), "Black")]
public Color CaretTickColor { get; set; }
FastColoredTextBox target;
[Description("Target FastColoredTextBox")]
public FastColoredTextBox Target
{
get { return target; }
set
{
if (target != null)
UnSubscribe(target);
target = value;
Subscribe(target);
OnTargetChanged();
}
}
public Ruler()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
MinimumSize = new Size(0, 24);
MaximumSize = new Size(int.MaxValue/2, 24);
BackColor2 = SystemColors.ControlLight;
TickColor = Color.DarkGray;
CaretTickColor = Color.Black;
}
protected virtual void OnTargetChanged()
{
if (TargetChanged != null)
TargetChanged(this, EventArgs.Empty);
}
protected virtual void UnSubscribe(FastColoredTextBox target)
{
target.Scroll -= new ScrollEventHandler(target_Scroll);
target.SelectionChanged -= new EventHandler(target_SelectionChanged);
target.VisibleRangeChanged -= new EventHandler(target_VisibleRangeChanged);
}
protected virtual void Subscribe(FastColoredTextBox target)
{
target.Scroll += new ScrollEventHandler(target_Scroll);
target.SelectionChanged += new EventHandler(target_SelectionChanged);
target.VisibleRangeChanged += new EventHandler(target_VisibleRangeChanged);
}
void target_VisibleRangeChanged(object sender, EventArgs e)
{
Invalidate();
}
void target_SelectionChanged(object sender, EventArgs e)
{
Invalidate();
}
protected virtual void target_Scroll(object sender, ScrollEventArgs e)
{
Invalidate();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
if (target == null)
return;
Point car = PointToClient(target.PointToScreen(target.PlaceToPoint(target.Selection.Start)));
Size fontSize = TextRenderer.MeasureText("W", Font);
int column = 0;
e.Graphics.FillRectangle(new LinearGradientBrush(new Rectangle(0, 0, Width, Height), BackColor, BackColor2, 270), new Rectangle(0, 0, Width, Height));
float columnWidth = target.CharWidth;
var sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Near;
var zeroPoint = target.PositionToPoint(0);
zeroPoint = PointToClient(target.PointToScreen(zeroPoint));
using (var pen = new Pen(TickColor))
using (var textBrush = new SolidBrush(ForeColor))
for (float x = zeroPoint.X; x < Right; x += columnWidth, ++column)
{
if (column % 10 == 0)
e.Graphics.DrawString(column.ToString(), Font, textBrush, x, 0f, sf);
e.Graphics.DrawLine(pen, (int)x, fontSize.Height + (column % 5 == 0 ? 1 : 3), (int)x, Height - 4);
}
using (var pen = new Pen(TickColor))
e.Graphics.DrawLine(pen, new Point(car.X - 3, Height - 3), new Point(car.X + 3, Height - 3));
using (var pen = new Pen(CaretTickColor))
{
e.Graphics.DrawLine(pen, new Point(car.X - 2, fontSize.Height + 3), new Point(car.X - 2, Height - 4));
e.Graphics.DrawLine(pen, new Point(car.X, fontSize.Height + 1), new Point(car.X, Height - 4));
e.Graphics.DrawLine(pen, new Point(car.X + 2, fontSize.Height + 3), new Point(car.X + 2, Height - 4));
}
}
}
}

View file

@ -0,0 +1,37 @@
namespace FastColoredTextBoxNS
{
partial class Ruler
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}

View file

@ -0,0 +1,430 @@
using System.Drawing;
using System;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
namespace FastColoredTextBoxNS
{
/// <summary>
/// Style of chars
/// </summary>
/// <remarks>This is base class for all text and design renderers</remarks>
public abstract class Style : IDisposable
{
/// <summary>
/// This style is exported to outer formats (HTML for example)
/// </summary>
public virtual bool IsExportable { get; set; }
/// <summary>
/// Occurs when user click on StyleVisualMarker joined to this style
/// </summary>
public event EventHandler<VisualMarkerEventArgs> VisualMarkerClick;
/// <summary>
/// Constructor
/// </summary>
public Style()
{
IsExportable = true;
}
/// <summary>
/// Renders given range of text
/// </summary>
/// <param name="gr">Graphics object</param>
/// <param name="position">Position of the range in absolute control coordinates</param>
/// <param name="range">Rendering range of text</param>
public abstract void Draw(Graphics gr, Point position, Range range);
/// <summary>
/// Occurs when user click on StyleVisualMarker joined to this style
/// </summary>
public virtual void OnVisualMarkerClick(FastColoredTextBox tb, VisualMarkerEventArgs args)
{
if (VisualMarkerClick != null)
VisualMarkerClick(tb, args);
}
/// <summary>
/// Shows VisualMarker
/// Call this method in Draw method, when you need to show VisualMarker for your style
/// </summary>
protected virtual void AddVisualMarker(FastColoredTextBox tb, StyleVisualMarker marker)
{
tb.AddVisualMarker(marker);
}
public static Size GetSizeOfRange(Range range)
{
return new Size((range.End.iChar - range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight);
}
public static GraphicsPath GetRoundedRectangle(Rectangle rect, int d)
{
GraphicsPath gp = new GraphicsPath();
gp.AddArc(rect.X, rect.Y, d, d, 180, 90);
gp.AddArc(rect.X + rect.Width - d, rect.Y, d, d, 270, 90);
gp.AddArc(rect.X + rect.Width - d, rect.Y + rect.Height - d, d, d, 0, 90);
gp.AddArc(rect.X, rect.Y + rect.Height - d, d, d, 90, 90);
gp.AddLine(rect.X, rect.Y + rect.Height - d, rect.X, rect.Y + d / 2);
return gp;
}
public virtual void Dispose()
{
;
}
/// <summary>
/// Returns CSS for export to HTML
/// </summary>
/// <returns></returns>
public virtual string GetCSS()
{
return "";
}
/// <summary>
/// Returns RTF descriptor for export to RTF
/// </summary>
/// <returns></returns>
public virtual RTFStyleDescriptor GetRTF()
{
return new RTFStyleDescriptor();
}
}
/// <summary>
/// Style for chars rendering
/// This renderer can draws chars, with defined fore and back colors
/// </summary>
public class TextStyle : Style
{
public Brush ForeBrush { get; set; }
public Brush BackgroundBrush { get; set; }
public FontStyle FontStyle { get; set; }
//public readonly Font Font;
public StringFormat stringFormat;
public TextStyle(Brush foreBrush, Brush backgroundBrush, FontStyle fontStyle)
{
this.ForeBrush = foreBrush;
this.BackgroundBrush = backgroundBrush;
this.FontStyle = fontStyle;
stringFormat = new StringFormat(StringFormatFlags.MeasureTrailingSpaces);
}
public override void Draw(Graphics gr, Point position, Range range)
{
//draw background
if (BackgroundBrush != null)
gr.FillRectangle(BackgroundBrush, position.X, position.Y, (range.End.iChar - range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight);
//draw chars
using(var f = new Font(range.tb.Font, FontStyle))
{
Line line = range.tb[range.Start.iLine];
float dx = range.tb.CharWidth;
float y = position.Y + range.tb.LineInterval/2;
float x = position.X - range.tb.CharWidth/3;
if (ForeBrush == null)
ForeBrush = new SolidBrush(range.tb.ForeColor);
if (range.tb.ImeAllowed)
{
//IME mode
for (int i = range.Start.iChar; i < range.End.iChar; i++)
{
SizeF size = FastColoredTextBox.GetCharSize(f, line[i].c);
var gs = gr.Save();
float k = size.Width > range.tb.CharWidth + 1 ? range.tb.CharWidth/size.Width : 1;
gr.TranslateTransform(x, y + (1 - k)*range.tb.CharHeight/2);
gr.ScaleTransform(k, (float) Math.Sqrt(k));
gr.DrawString(line[i].c.ToString(), f, ForeBrush, 0, 0, stringFormat);
gr.Restore(gs);
x += dx;
}
}
else
{
//classic mode
for (int i = range.Start.iChar; i < range.End.iChar; i++)
{
//draw char
gr.DrawString(line[i].c.ToString(), f, ForeBrush, x, y, stringFormat);
x += dx;
}
}
}
}
public override string GetCSS()
{
string result = "";
if (BackgroundBrush is SolidBrush)
{
var s = ExportToHTML.GetColorAsString((BackgroundBrush as SolidBrush).Color);
if (s != "")
result += "background-color:" + s + ";";
}
if (ForeBrush is SolidBrush)
{
var s = ExportToHTML.GetColorAsString((ForeBrush as SolidBrush).Color);
if (s != "")
result += "color:" + s + ";";
}
if ((FontStyle & FontStyle.Bold) != 0)
result += "font-weight:bold;";
if ((FontStyle & FontStyle.Italic) != 0)
result += "font-style:oblique;";
if ((FontStyle & FontStyle.Strikeout) != 0)
result += "text-decoration:line-through;";
if ((FontStyle & FontStyle.Underline) != 0)
result += "text-decoration:underline;";
return result;
}
public override RTFStyleDescriptor GetRTF()
{
var result = new RTFStyleDescriptor();
if (BackgroundBrush is SolidBrush)
result.BackColor = (BackgroundBrush as SolidBrush).Color;
if (ForeBrush is SolidBrush)
result.ForeColor = (ForeBrush as SolidBrush).Color;
if ((FontStyle & FontStyle.Bold) != 0)
result.AdditionalTags += @"\b";
if ((FontStyle & FontStyle.Italic) != 0)
result.AdditionalTags += @"\i";
if ((FontStyle & FontStyle.Strikeout) != 0)
result.AdditionalTags += @"\strike";
if ((FontStyle & FontStyle.Underline) != 0)
result.AdditionalTags += @"\ul";
return result;
}
}
/// <summary>
/// Renderer for folded block
/// </summary>
public class FoldedBlockStyle : TextStyle
{
public FoldedBlockStyle(Brush foreBrush, Brush backgroundBrush, FontStyle fontStyle):
base(foreBrush, backgroundBrush, fontStyle)
{
}
public override void Draw(Graphics gr, Point position, Range range)
{
if (range.End.iChar > range.Start.iChar)
{
base.Draw(gr, position, range);
int firstNonSpaceSymbolX = position.X;
//find first non space symbol
for (int i = range.Start.iChar; i < range.End.iChar; i++)
if (range.tb[range.Start.iLine][i].c != ' ')
break;
else
firstNonSpaceSymbolX += range.tb.CharWidth;
//create marker
range.tb.AddVisualMarker(new FoldedAreaMarker(range.Start.iLine, new Rectangle(firstNonSpaceSymbolX, position.Y, position.X + (range.End.iChar - range.Start.iChar) * range.tb.CharWidth - firstNonSpaceSymbolX, range.tb.CharHeight)));
}
else
{
//draw '...'
using(Font f = new Font(range.tb.Font, FontStyle))
gr.DrawString("...", f, ForeBrush, range.tb.LeftIndent, position.Y - 2);
//create marker
range.tb.AddVisualMarker(new FoldedAreaMarker(range.Start.iLine, new Rectangle(range.tb.LeftIndent + 2, position.Y, 2 * range.tb.CharHeight, range.tb.CharHeight)));
}
}
}
/// <summary>
/// Renderer for selected area
/// </summary>
public class SelectionStyle : Style
{
public Brush BackgroundBrush{ get; set;}
public Brush ForegroundBrush { get; private set; }
public override bool IsExportable
{
get{return false;} set{}
}
public SelectionStyle(Brush backgroundBrush, Brush foregroundBrush = null)
{
this.BackgroundBrush = backgroundBrush;
this.ForegroundBrush = foregroundBrush;
}
public override void Draw(Graphics gr, Point position, Range range)
{
//draw background
if (BackgroundBrush != null)
{
gr.SmoothingMode = SmoothingMode.None;
var rect = new Rectangle(position.X, position.Y, (range.End.iChar - range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight);
if (rect.Width == 0)
return;
gr.FillRectangle(BackgroundBrush, rect);
//
if (ForegroundBrush != null)
{
//draw text
gr.SmoothingMode = SmoothingMode.AntiAlias;
var r = new Range(range.tb, range.Start.iChar, range.Start.iLine,
Math.Min(range.tb[range.End.iLine].Count, range.End.iChar), range.End.iLine);
using (var style = new TextStyle(ForegroundBrush, null, FontStyle.Regular))
style.Draw(gr, new Point(position.X, position.Y - 1), r);
}
}
}
}
/// <summary>
/// Marker style
/// Draws background color for text
/// </summary>
public class MarkerStyle : Style
{
public Brush BackgroundBrush{get;set;}
public MarkerStyle(Brush backgroundBrush)
{
this.BackgroundBrush = backgroundBrush;
IsExportable = true;
}
public override void Draw(Graphics gr, Point position, Range range)
{
//draw background
if (BackgroundBrush != null)
{
Rectangle rect = new Rectangle(position.X, position.Y, (range.End.iChar - range.Start.iChar) * range.tb.CharWidth, range.tb.CharHeight);
if (rect.Width == 0)
return;
gr.FillRectangle(BackgroundBrush, rect);
}
}
public override string GetCSS()
{
string result = "";
if (BackgroundBrush is SolidBrush)
{
var s = ExportToHTML.GetColorAsString((BackgroundBrush as SolidBrush).Color);
if (s != "")
result += "background-color:" + s + ";";
}
return result;
}
}
/// <summary>
/// Draws small rectangle for popup menu
/// </summary>
public class ShortcutStyle : Style
{
public Pen borderPen;
public ShortcutStyle(Pen borderPen)
{
this.borderPen = borderPen;
}
public override void Draw(Graphics gr, Point position, Range range)
{
//get last char coordinates
Point p = range.tb.PlaceToPoint(range.End);
//draw small square under char
Rectangle rect = new Rectangle(p.X - 5, p.Y + range.tb.CharHeight - 2, 4, 3);
gr.FillPath(Brushes.White, GetRoundedRectangle(rect, 1));
gr.DrawPath(borderPen, GetRoundedRectangle(rect, 1));
//add visual marker for handle mouse events
AddVisualMarker(range.tb, new StyleVisualMarker(new Rectangle(p.X-range.tb.CharWidth, p.Y, range.tb.CharWidth, range.tb.CharHeight), this));
}
}
/// <summary>
/// This style draws a wavy line below a given text range.
/// </summary>
/// <remarks>Thanks for Yallie</remarks>
public class WavyLineStyle : Style
{
private Pen Pen { get; set; }
public WavyLineStyle(int alpha, Color color)
{
Pen = new Pen(Color.FromArgb(alpha, color));
}
public override void Draw(Graphics gr, Point pos, Range range)
{
var size = GetSizeOfRange(range);
var start = new Point(pos.X, pos.Y + size.Height - 1);
var end = new Point(pos.X + size.Width, pos.Y + size.Height - 1);
DrawWavyLine(gr, start, end);
}
private void DrawWavyLine(Graphics graphics, Point start, Point end)
{
if (end.X - start.X < 2)
{
graphics.DrawLine(Pen, start, end);
return;
}
var offset = -1;
var points = new List<Point>();
for (int i = start.X; i <= end.X; i += 2)
{
points.Add(new Point(i, start.Y + offset));
offset = -offset;
}
graphics.DrawLines(Pen, points.ToArray());
}
public override void Dispose()
{
base.Dispose();
if (Pen != null)
Pen.Dispose();
}
}
/// <summary>
/// This style is used to mark range of text as ReadOnly block
/// </summary>
/// <remarks>You can inherite this style to add visual effects of readonly text</remarks>
public class ReadOnlyStyle : Style
{
public ReadOnlyStyle()
{
IsExportable = false;
}
public override void Draw(Graphics gr, Point position, Range range)
{
//
}
}
}

View file

@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System;
namespace FastColoredTextBoxNS
{
public class SyntaxDescriptor: IDisposable
{
public char leftBracket = '(';
public char rightBracket = ')';
public char leftBracket2 = '{';
public char rightBracket2 = '}';
public BracketsHighlightStrategy bracketsHighlightStrategy = BracketsHighlightStrategy.Strategy2;
public readonly List<Style> styles = new List<Style>();
public readonly List<RuleDesc> rules = new List<RuleDesc>();
public readonly List<FoldingDesc> foldings = new List<FoldingDesc>();
public void Dispose()
{
foreach (var style in styles)
style.Dispose();
}
}
public class RuleDesc
{
Regex regex;
public string pattern;
public RegexOptions options = RegexOptions.None;
public Style style;
public Regex Regex
{
get
{
if (regex == null)
{
regex = new Regex(pattern, SyntaxHighlighter.RegexCompiledOption | options);
}
return regex;
}
}
}
public class FoldingDesc
{
public string startMarkerRegex;
public string finishMarkerRegex;
public RegexOptions options = RegexOptions.None;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Drawing;
using System.IO;
namespace FastColoredTextBoxNS
{
/// <summary>
/// This class contains the source text (chars and styles).
/// It stores a text lines, the manager of commands, undo/redo stack, styles.
/// </summary>
public class TextSource: IList<Line>, IDisposable
{
readonly protected List<Line> lines = new List<Line>();
protected LinesAccessor linesAccessor;
int lastLineUniqueId;
public CommandManager Manager { get; set; }
FastColoredTextBox currentTB;
/// <summary>
/// Styles
/// </summary>
public readonly Style[] Styles;
/// <summary>
/// Occurs when line was inserted/added
/// </summary>
public event EventHandler<LineInsertedEventArgs> LineInserted;
/// <summary>
/// Occurs when line was removed
/// </summary>
public event EventHandler<LineRemovedEventArgs> LineRemoved;
/// <summary>
/// Occurs when text was changed
/// </summary>
public event EventHandler<TextChangedEventArgs> TextChanged;
/// <summary>
/// Occurs when recalc is needed
/// </summary>
public event EventHandler<TextChangedEventArgs> RecalcNeeded;
/// <summary>
/// Occurs when recalc wordwrap is needed
/// </summary>
public event EventHandler<TextChangedEventArgs> RecalcWordWrap;
/// <summary>
/// Occurs before text changing
/// </summary>
public event EventHandler<TextChangingEventArgs> TextChanging;
/// <summary>
/// Occurs after CurrentTB was changed
/// </summary>
public event EventHandler CurrentTBChanged;
/// <summary>
/// Current focused FastColoredTextBox
/// </summary>
public FastColoredTextBox CurrentTB {
get { return currentTB; }
set {
if (currentTB == value)
return;
currentTB = value;
OnCurrentTBChanged();
}
}
public virtual void ClearIsChanged()
{
foreach(var line in lines)
line.IsChanged = false;
}
public virtual Line CreateLine()
{
return new Line(GenerateUniqueLineId());
}
private void OnCurrentTBChanged()
{
if (CurrentTBChanged != null)
CurrentTBChanged(this, EventArgs.Empty);
}
/// <summary>
/// Default text style
/// This style is using when no one other TextStyle is not defined in Char.style
/// </summary>
public TextStyle DefaultStyle { get; set; }
public TextSource(FastColoredTextBox currentTB)
{
this.CurrentTB = currentTB;
linesAccessor = new LinesAccessor(this);
Manager = new CommandManager(this);
if (Enum.GetUnderlyingType(typeof(StyleIndex)) == typeof(UInt32))
Styles = new Style[32];
else
Styles = new Style[16];
InitDefaultStyle();
}
public virtual void InitDefaultStyle()
{
DefaultStyle = new TextStyle(null, null, FontStyle.Regular);
}
public virtual Line this[int i]
{
get{
return lines[i];
}
set {
throw new NotImplementedException();
}
}
public virtual bool IsLineLoaded(int iLine)
{
return lines[iLine] != null;
}
/// <summary>
/// Text lines
/// </summary>
public virtual IList<string> GetLines()
{
return linesAccessor;
}
public IEnumerator<Line> GetEnumerator()
{
return lines.GetEnumerator();
}
IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (lines as IEnumerator);
}
public virtual int BinarySearch(Line item, IComparer<Line> comparer)
{
return lines.BinarySearch(item, comparer);
}
public virtual int GenerateUniqueLineId()
{
return lastLineUniqueId++;
}
public virtual void InsertLine(int index, Line line)
{
lines.Insert(index, line);
OnLineInserted(index);
}
public virtual void OnLineInserted(int index)
{
OnLineInserted(index, 1);
}
public virtual void OnLineInserted(int index, int count)
{
if (LineInserted != null)
LineInserted(this, new LineInsertedEventArgs(index, count));
}
public virtual void RemoveLine(int index)
{
RemoveLine(index, 1);
}
public virtual bool IsNeedBuildRemovedLineIds
{
get { return LineRemoved != null; }
}
public virtual void RemoveLine(int index, int count)
{
List<int> removedLineIds = new List<int>();
//
if (count > 0)
if (IsNeedBuildRemovedLineIds)
for (int i = 0; i < count; i++)
removedLineIds.Add(this[index + i].UniqueId);
//
lines.RemoveRange(index, count);
OnLineRemoved(index, count, removedLineIds);
}
public virtual void OnLineRemoved(int index, int count, List<int> removedLineIds)
{
if (count > 0)
if (LineRemoved != null)
LineRemoved(this, new LineRemovedEventArgs(index, count, removedLineIds));
}
public virtual void OnTextChanged(int fromLine, int toLine)
{
if (TextChanged != null)
TextChanged(this, new TextChangedEventArgs(Math.Min(fromLine, toLine), Math.Max(fromLine, toLine) ));
}
public class TextChangedEventArgs : EventArgs
{
public int iFromLine;
public int iToLine;
public TextChangedEventArgs(int iFromLine, int iToLine)
{
this.iFromLine = iFromLine;
this.iToLine = iToLine;
}
}
public virtual int IndexOf(Line item)
{
return lines.IndexOf(item);
}
public virtual void Insert(int index, Line item)
{
InsertLine(index, item);
}
public virtual void RemoveAt(int index)
{
RemoveLine(index);
}
public virtual void Add(Line item)
{
InsertLine(Count, item);
}
public virtual void Clear()
{
RemoveLine(0, Count);
}
public virtual bool Contains(Line item)
{
return lines.Contains(item);
}
public virtual void CopyTo(Line[] array, int arrayIndex)
{
lines.CopyTo(array, arrayIndex);
}
/// <summary>
/// Lines count
/// </summary>
public virtual int Count
{
get { return lines.Count; }
}
public virtual bool IsReadOnly
{
get { return false; }
}
public virtual bool Remove(Line item)
{
int i = IndexOf(item);
if (i >= 0)
{
RemoveLine(i);
return true;
}
else
return false;
}
public virtual void NeedRecalc(TextChangedEventArgs args)
{
if (RecalcNeeded != null)
RecalcNeeded(this, args);
}
public virtual void OnRecalcWordWrap(TextChangedEventArgs args)
{
if (RecalcWordWrap != null)
RecalcWordWrap(this, args);
}
public virtual void OnTextChanging()
{
string temp = null;
OnTextChanging(ref temp);
}
public virtual void OnTextChanging(ref string text)
{
if (TextChanging != null)
{
var args = new TextChangingEventArgs() { InsertingText = text };
TextChanging(this, args);
text = args.InsertingText;
if (args.Cancel)
text = string.Empty;
};
}
public virtual int GetLineLength(int i)
{
return lines[i].Count;
}
public virtual bool LineHasFoldingStartMarker(int iLine)
{
return !string.IsNullOrEmpty(lines[iLine].FoldingStartMarker);
}
public virtual bool LineHasFoldingEndMarker(int iLine)
{
return !string.IsNullOrEmpty(lines[iLine].FoldingEndMarker);
}
public virtual void Dispose()
{
;
}
public virtual void SaveToFile(string fileName, Encoding enc)
{
using (StreamWriter sw = new StreamWriter(fileName, false, enc))
{
for (int i = 0; i < Count - 1;i++ )
sw.WriteLine(lines[i].Text);
sw.Write(lines[Count-1].Text);
}
}
}
}

View file

@ -0,0 +1,96 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
///
/// These classes are required for correct data binding to Text property of FastColoredTextbox
///
class FCTBDescriptionProvider : TypeDescriptionProvider
{
public FCTBDescriptionProvider(Type type)
: base(GetDefaultTypeProvider(type))
{
}
private static TypeDescriptionProvider GetDefaultTypeProvider(Type type)
{
return TypeDescriptor.GetProvider(type);
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
ICustomTypeDescriptor defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
return new FCTBTypeDescriptor(defaultDescriptor, instance);
}
}
class FCTBTypeDescriptor : CustomTypeDescriptor
{
ICustomTypeDescriptor parent;
object instance;
public FCTBTypeDescriptor(ICustomTypeDescriptor parent, object instance)
: base(parent)
{
this.parent = parent;
this.instance = instance;
}
public override string GetComponentName()
{
var ctrl = (instance as Control);
return ctrl == null ? null : ctrl.Name;
}
public override EventDescriptorCollection GetEvents()
{
var coll = base.GetEvents();
var list = new EventDescriptor[coll.Count];
for (int i = 0; i < coll.Count; i++)
if (coll[i].Name == "TextChanged")//instead of TextChanged slip BindingTextChanged for binding
list[i] = new FooTextChangedDescriptor(coll[i]);
else
list[i] = coll[i];
return new EventDescriptorCollection(list);
}
}
class FooTextChangedDescriptor : EventDescriptor
{
public FooTextChangedDescriptor(MemberDescriptor desc)
: base(desc)
{
}
public override void AddEventHandler(object component, Delegate value)
{
(component as FastColoredTextBox).BindingTextChanged += value as EventHandler;
}
public override Type ComponentType
{
get { return typeof(FastColoredTextBox); }
}
public override Type EventType
{
get { return typeof(EventHandler); }
}
public override bool IsMulticast
{
get { return true; }
}
public override void RemoveEventHandler(object component, Delegate value)
{
(component as FastColoredTextBox).BindingTextChanged -= value as EventHandler;
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
[System.ComponentModel.ToolboxItem(false)]
public class UnfocusablePanel : UserControl
{
public Color BackColor2 { get; set; }
public Color BorderColor { get; set; }
public new string Text { get; set; }
public StringAlignment TextAlignment { get; set; }
public UnfocusablePanel()
{
SetStyle(ControlStyles.Selectable, false);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
using (var brush = new LinearGradientBrush(ClientRectangle, BackColor2, BackColor, 90))
e.Graphics.FillRectangle(brush, 0, 0, ClientSize.Width - 1, ClientSize.Height - 1);
using(var pen = new Pen(BorderColor))
e.Graphics.DrawRectangle(pen, 0, 0, ClientSize.Width - 1, ClientSize.Height - 1);
if (!string.IsNullOrEmpty(Text))
{
StringFormat sf = new StringFormat();
sf.Alignment = TextAlignment;
sf.LineAlignment = StringAlignment.Center;
using(var brush = new SolidBrush(ForeColor))
e.Graphics.DrawString(Text, Font, brush, new RectangleF(1, 1, ClientSize.Width - 2, ClientSize.Height - 2), sf);
}
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace FastColoredTextBoxNS
{
public class VisualMarker
{
public readonly Rectangle rectangle;
public VisualMarker(Rectangle rectangle)
{
this.rectangle = rectangle;
}
public virtual void Draw(Graphics gr, Pen pen)
{
}
public virtual Cursor Cursor
{
get { return Cursors.Hand; }
}
}
public class CollapseFoldingMarker: VisualMarker
{
public readonly int iLine;
public CollapseFoldingMarker(int iLine, Rectangle rectangle)
: base(rectangle)
{
this.iLine = iLine;
}
public void Draw(Graphics gr, Pen pen, Brush backgroundBrush, Pen forePen)
{
//draw minus
gr.FillRectangle(backgroundBrush, rectangle);
gr.DrawRectangle(pen, rectangle);
gr.DrawLine(forePen, rectangle.Left + 2, rectangle.Top + rectangle.Height / 2, rectangle.Right - 2, rectangle.Top + rectangle.Height / 2);
}
}
public class ExpandFoldingMarker : VisualMarker
{
public readonly int iLine;
public ExpandFoldingMarker(int iLine, Rectangle rectangle)
: base(rectangle)
{
this.iLine = iLine;
}
public void Draw(Graphics gr, Pen pen, Brush backgroundBrush, Pen forePen)
{
//draw plus
gr.FillRectangle(backgroundBrush, rectangle);
gr.DrawRectangle(pen, rectangle);
gr.DrawLine(forePen, rectangle.Left + 2, rectangle.Top + rectangle.Height / 2, rectangle.Right - 2, rectangle.Top + rectangle.Height / 2);
gr.DrawLine(forePen, rectangle.Left + rectangle.Width / 2, rectangle.Top + 2, rectangle.Left + rectangle.Width / 2, rectangle.Bottom - 2);
}
}
public class FoldedAreaMarker : VisualMarker
{
public readonly int iLine;
public FoldedAreaMarker(int iLine, Rectangle rectangle)
: base(rectangle)
{
this.iLine = iLine;
}
public override void Draw(Graphics gr, Pen pen)
{
gr.DrawRectangle(pen, rectangle);
}
}
public class StyleVisualMarker : VisualMarker
{
public Style Style{get;private set;}
public StyleVisualMarker(Rectangle rectangle, Style style)
: base(rectangle)
{
this.Style = style;
}
}
public class VisualMarkerEventArgs : MouseEventArgs
{
public Style Style { get; private set; }
public StyleVisualMarker Marker { get; private set; }
public VisualMarkerEventArgs(Style style, StyleVisualMarker marker, MouseEventArgs args)
: base(args.Button, args.Clicks, args.X, args.Y, args.Delta)
{
this.Style = style;
this.Marker = marker;
}
}
}

Some files were not shown because too many files have changed in this diff Show more