Sign in to follow this  
  • entries
    23
  • comments
    28
  • views
    20191

Twas the night before Christmas...

Sign in to follow this  

147 views

Well, technically tis the "almost evening before Christmas", but whatever.

rnfnCodeLite



CollapseData has been added, but still needs fleshing out, probably when I implement the collapsing code. TextNode should be done. I made a little undo/redo engine, wittly named UndoRedo. Text, too, should be done.

rnfn.Collections.UndoRedo
Here is the source for it(seriously, though, why the blazing freaking blasted deuce isn't "Select All" on the menu that pops up when you right-click the text area in VS? It doesn't make any sense for it not to be there![disturbed]):
using System;
using System.Collections;

namespace rnfn.Collections
{
internal class UndoRedo
{
private ArrayList undoStack;
private ArrayList redoStack;
private bool ignorePushes;

public UndoRedo()
{
undoStack=new ArrayList();
redoStack=new ArrayList();
ignorePushes=false;
}

public T PopUndo()
{
if(undoStack.Count==0)
return default(T);

T temp=(T)undoStack[undoStack.Count-1];
undoStack.RemoveAt(undoStack.Count-1);
redoStack.Add(temp);
return temp;
}

public T Undo()
{
return PopUndo();
}

public T PeekUndo()
{
if(undoStack.Count==0)
return default(T);
return (T)undoStack[undoStack.Count-1];
}

public T[] PeekAllUndo()
{
if(undoStack.Count==0)
return default(T[]);
return (T[])undoStack.ToArray(typeof(T));
}

public void PushUndo(T data)
{
if(!ignorePushes)
{
undoStack.Add(data);
redoStack.Clear();
}
}

public void PushUndo(T[] data)
{
if(!ignorePushes)
if(data!=null)
undoStack.AddRange(data);
}

public T PopRedo()
{
if(redoStack.Count==0)
return default(T);

T temp=(T)redoStack[redoStack.Count-1];
redoStack.RemoveAt(redoStack.Count-1);
undoStack.Add(temp);
return temp;
}

public T Redo()
{
return PopRedo();
}

public T PeekRedo()
{
if(redoStack.Count==0)
return default(T);
return (T)redoStack[redoStack.Count-1];
}

public T[] PeekAllRedo()
{
if(redoStack.Count==0)
return null;
return (T[])redoStack.ToArray(typeof(T));
}

public void PushRedo(T data)
{
if(!ignorePushes)
redoStack.Add(data);
}

public void PushRedo(T[] data)
{
if(!ignorePushes)
if(data!=null)
redoStack.AddRange(data);
}

public bool IgnorePushes
{
get{return ignorePushes;}
set{ignorePushes=value;}
}
}
}

It seemed simple enough to write. You might say...overly simple. If you see any mistakes or problems with it, pointing them out would be much appreciated. I used two arrays to allow multiple items to be peeked or pushed without too much trouble. The only multiple action that I didn't include was the actual undoing/redoing, as I thought it might lead to a wee bit o' confusion. Though I might end up adding that feature before all is said and done with. I think I got the push/peek/pop terms correctly. Embarrasingly enough, I was 75% of the way done with this code when I realized I'd borked it up by having poke in there instead one of them(I think pop?), so meh.

The main thing, though, that I would like to point out is the IgnorePushes property. The reason I added this is because the Text code that is called to undo/redo the text merely sends the data to either the AddText or RemoveText, depending on which is appropriate, both of which in turn make use of the undo/redo engine. To avoid problems where multiple undoing would just toggle between two actions, the undo/redo functions set the undo/redo engine to ignore the pushes, so no toggling.

rnfn.Controls.CodeLite.Text
using System;
using System.Collections.Generic;
using System.Text;

using rnfn.Collections;

namespace rnfn.Controls.CodeLite
{
internal class Text
{
internal class TextAction
{
internal enum Activity{ADDING,REMOVING};
public int line;
public int charOffset;
public Activity activity;
public string text;

internal TextAction(int line,int charOffset,Activity activity,string text)
{
this.line=line;
this.charOffset=charOffset;
this.activity=activity;
this.text=text;
}

internal static TextAction AddText(int line,int charOffset,string text)
{
return new TextAction(line,charOffset,Activity.ADDING,text);
}

internal static TextAction DeleteText(int line,int charOffset,string text)
{
return new TextAction(line,charOffset,Activity.REMOVING,text);
}
}

private LinkedList text;
private UndoRedo undoredoEngine;

public Text()
{
text=new LinkedList();
text.AddFirst(new TextNode());
}

public void Undo()
{
TextAction temp=undoredoEngine.Undo();

if(temp==null)
return;

undoredoEngine.IgnorePushes=true;

if(temp.activity==TextAction.Activity.ADDING)
{
RemoveText(GetLineFromNumber(temp.line),temp.charOffset,temp.text.Length);
}
else if(temp.activity==TextAction.Activity.REMOVING)
{
AddText(GetLineFromNumber(temp.line),temp.charOffset,temp.text);
}

undoredoEngine.IgnorePushes=false;
}
public void Redo()
{
TextAction temp=undoredoEngine.Redo();

if(temp==null)
return;

undoredoEngine.IgnorePushes=true;

if(temp.activity==TextAction.Activity.ADDING)
{
AddText(GetLineFromNumber(temp.line),temp.charOffset,temp.text);
}
else if(temp.activity==TextAction.Activity.REMOVING)
{
RemoveText(GetLineFromNumber(temp.line),temp.charOffset,temp.text.Length);
}

undoredoEngine.IgnorePushes=false;
}

//TODO: Optimize
public int GetNumberFromLine(LinkedListNode node)
{
int line=0;
LinkedListNode temp=text.First;

while(temp!=node)
{
if(temp.Next==null)
return -1;
temp=temp.Next;
line++;
}
return line;
}
//TODO: Optimize
public LinkedListNode GetLineFromNumber(int lineNumber)
{
if(lineNumber<0)
return null;

LinkedListNode temp=text.First;

for(int i=1;i<=lineNumber;i++)
temp=temp.Next;

return temp;
}

public void AddText(LinkedListNode line,int charOffset,string text)
{
line.Value.text=line.Value.text.PadRight(charOffset,' ')+text;

if(line.Next!=null)
{
line.Value.text+="\n"+line.Next.Value.text;
this.text.Remove(line.Next);
}

UnNewlineify(line);
}
public void RemoveText(LinkedListNode line,int charOffset,int length)
{
int tempLength=0,tempLines=0;
LinkedListNode tempNode=line;
tempLength=line.Value.text.Length-charOffset;

while(tempLength {
if(tempNode.Next!=null)
{
tempNode=tempNode.Next;
tempLength+=tempNode.Value.text.Length;
}
else
{
length=tempLength;
}
}

Newlineify(line,tempLines);
line.Value.text.Remove(charOffset,length+tempLines);
//HACK: Shouldn't need this line...
UnNewlineify(line);
}

private void UnNewlineify(LinkedListNode node)
{
string[] temp=node.Value.text.Split('\n');

for(int i=temp.Length-1;i>0;i--)
text.AddAfter(node,new TextNode(temp));

node.Value.text=temp[0];
}
private void Newlineify(LinkedListNode node,int lines)
{
while(node.Next!=null && --lines>0)
{
node.Value.text=node.Next.Value.text;
text.Remove(node.Next);
}
}

public static string CRLN2LN(string text)
{
return text.Replace("\r\n","\n");
}
public static string LN2CRLN(string text)
{
//The double replace is in case there are mixed line feeds provided,
// in which case an "\r\n" would become "\r\r\n" under one replace.
return text.Replace("\r\n","\n").Replace("\n","\r\n");
}
}
}

This is my current Text class, very much modified from my previous one, for those of you that recall it. If any of you do. Keep in mind, that none of it is tested, yet, so there may be glaring bugs to you that I've yet to stumble upon.

TextAction is for UndoRedo. It did have an extra field, public string message;, but since undo/redo will, at this point in time, be handled on a character by character basis(except for the instances where portions of text are added, such as pastes or code-side adding), so having a message with the value of "Added the text:'c'" and the text field having a value of "c" makes very little sense. I'll probably add extra values to the Activity enum for events such as copying/cutting/pasted/etc.

GetLineFromNumber and GetNumberFromLine are still used to convert a LinkedListNode to a line number, and vice versa.

AddText and RemoveText are my biggest 'concern' in terms of bugginess. AddText is pretty much based on the old AddText, and RemoveText was designed...'inversely' to it, in a sense. It just seems to clean, and a bit smooth with the Undo and Redo functions, for it to be real for me. There has got to be a glaring bug I'm not seeing. Oh well, guess I'll just have to test it a few times.

Newlineify and UnNewlineify are used by AddText and RemoveText to handle the possibility of the adding or removal of the line feed character, '\n'. '\n' is the 'officialy' newline char for the control, but, of course, it can't really be part of any line.

CRLN2LN and LN2CRLN are my first and current attempts at also supporting other versions of newline seperators(are there any other accepted ones, other than the two here?) while maintaining the internal '\n' as the line seperator. I don't think I've used either of these, but there will be an option to add and view text with the "\r\n" newline as well as "\n". Again, these are my first, horrid attempts at this, so they'll probably be redone or altered or something at somepoint.


No rants, or anything else, really, today. I've got coding to do and gaming to game.

Thanks for reading, and have a very Merry Chrismahanakwanzika!
[Disclaimer: No review for errors was made after the writing of this post. I've got other things to do tonight!]
Sign in to follow this  


2 Comments


Recommended Comments

In your UndoRedo class' PushUndo(T[] data) function, did you forget to clear the redo stack? Other than that, the UndoRedo code was surprisingly clear (clear enough for a noob who isn't sure what language that is (though it bears some similarity to c++, so c# maybe?) to follow it). It looks pretty good to me.

I have no feedback for your text class though, I got lost :).

Happy soon to be new year!

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now