How to make TSynEdit's Wordwrap same as TMemo's? - delphi

I'm using TSynEdit as a more user-friendly TMemo, mostly for the advanced shortcuts, UNDO/REDO, and so on.
Other things are OK, except the wordwrap behavior, please check the attached screenshot below, SynEdit has a strange space shown on the left-most side.
How to avoid that and make it look like TMemo?
The TSynEdit's key property settings:
synEdit1.UseCodeFolding := False;
synEdit1.Options := [eoAutoIndent, eoDragDropEditing, eoEnhanceEndKey,
eoGroupUndo, eoScrollPastEol, eoSmartTabDelete,
eoSmartTabs, eoTabsToSpaces];
synEdit1.ScrollBars := ssVertical;
synEdit1.TabWidth := 4;
synEdit1.WantTabs := True;
synEdit1.WordWrap := True;
synEdit1.FontSmoothing := fsmNone;

This is not a complete, tested answer to the q, but may offer the determined
reader a jumping-of point to a functional solution.
The word-wrapping behaviour of a TSynEdit is determined by its current
TSynWordWrapPlugin. The default plugin is defined in SynEditWordWrap.Pas
and contains the procedure TSynWordWrapPlugin.WrapLines method, starting at
line 512 in the version I downloaded yesterday using the D10.2.3 GetIt Manager.
Starting at line 560 there is the block of code which, as far as I can tell,
accounts for the space at the start of each wrapped line as illustrated in the q:
if Editor.IsWordBreakChar(vRunner^) then
begin
vRowEnd := vRunner;
break;
end;
Dec(vRunner);
vRunner and vRowEnd are among a number of PWideChar variables used in the WrapLines method.
Watching the behaviour of this code, which is inside a while loop (which is looking for a place to do a word-wrap), it operates
so that when Editor.IsWordBreakChar(vRunner^) returns true, the vRunner pointer
has already moved backwards past the word-break char, which is why it (the space) ends
up on the following line, causing the problem noted by the OP.
Changing the code to
if Editor.IsWordBreakChar(vRunner^) then
begin
{ma} Inc(vRunner); // WARNING: not fully tested
vRowEnd := vRunner;
break;
end;
Dec(vRunner);
forces the vRunner pointer forwards past the word-break character so that the space is included at the end of the line rather than at the start of the next one, so the SynEdit
then displays its wrapped text like the standard TMemo.
Personally, I would not use this change, but would instead see if I could persuade
the SynEdit developers to provide an official solution. if I did use the change
shown above, I certainly wouldn't do it by changing the source of SynEditWordWrap.Pas,
I would do it by writing a replacement for TSynWordWrapPlugin and I would include a check that the inc(vRunner) does not exceed the valid bounds of the buffer being used to do the word-wrapping.

Related

Format and Pointers/Hex Values (Memory Overwrite)

In my Delphi XE2 32-bit application (Update 4 Hotfix 1 Version 16.0.4504.48759) , I'm using the Format() routine to log pointer values.
For example:
Format('MyObject (%p)', [Pointer(MyObject)]);
However, the resulting string sometimes contains garbage characters (e.g., in this case '?' or '|' in place of hex digits):
MyObject (4E?|2010)
I also get the same result when replacing '%p' with '%x' like so:
Format('MyObject (%x)', [Integer(MyObject)]);
However, using an integer value always works:
Format('MyObject (%d)', [Integer(MyObject)]);
MyObject (1291453120)
Is there a bug that I'm unaware of or can this be related to the problem experienced here?
Why does Format crash when anything but "%s" is used with a Variant?
UPDATE
I've accepted Jeroen's answer as it led me to the solution by process of elimination. After the situation with starting the app via F7 (as per the comment), I figured that something must be going wrong much earlier in the process. On a hunch, I disabled madExcept from its IDE menu, rebuilt the app, and the problem disappeared. Evidently, whatever code madExcept was linking into my application was causing an overwrite in the SysUtils constant TwoHexLookup. Re-enabling madExcept and rebuilding (without any other changes on my part) also worked, so there must have been some corruption during the linking phase.
The strategy Jeroen outlined for detecting memory corruption was a useful exercise and should prove valuable if I encounter a similar situation.
My best hypothesis is that your code is modifying some memory that it shouldn't, perhaps by dereferencing an uninitialized pointer. I've created a reproducible case that demonstrates this possibility. At least, it's reproducible on my machine with my version of the compiler. The exact same code might not do the same thing in another circumstance.
procedure TForm1.Button1Click(Sender: TObject);
var
P : pbyte;
S : string;
T : ansistring;
begin
// There's nothing special about HexDisplayPrefix.
// It just happens to be one of the last global variables
// declared in SysUtils.
P := # ( HexDisplayPrefix );
// A few bytes beyond that is TwoHexLookUp.
// This is a static array of unicode characters used by the IntToHex routine,
// which is in turn used by Format when %p or %x are used.
// I'll add an offset to P so that it points into that array.
// I'll make the offset odd so that it points to the high byte of a character.
// Of course, I can't guarantee that the same offset will work for you
P := P + 5763;
// Change the lookup table.
// Of course, you would never do this on purpose.
P ^ := 39;
// Now let's call IntToHex
S := IntToHex ( $E0, 2 );
// Show the value on the screen.
// Hey look, the zero has been replaced with a star.
memo1 . lines . add ( S );
// Convert the unicode string to an ansistring
T := ansistring ( S );
// Show the value on the screen.
// When converting to Ansi, the system doesn't know what to do with the star,
// so it replaces it with a question mark.
memo1 . lines . add ( unicodestring(T) );
end;
As this appears to be a memory overwrite (cf. your comment to user1008646) you can try to follow these steps:
First try to find out which memory address gets overwritten. You mention that s := IntToHex(2129827392, 8); fails. Find out the correct value, then find out if it is within TwoHexLookUp.
If it is within TwoHexLookUp, then set a data-changed breakpoint (see How to define a breakpoint whenever an object field value changes? and Add data breakpoint on how to do this).
Run your app until the breakpoint fires.
Ad 1: probably the easiest way is to look into TwoHexLookUp which value change has the same effect to get the wrong result from s := IntToHex(2129827392, 8); as you observe at run-time.
Thursday I'm doing some Delphi work at a client, so then I might have time to dig a bit deeper.
Edit
When you step through your process with F7, you indeed get into the SysInit first.
What you can do there is already set a breakpoint on the TwoHexLookup array.
Then either F9/F8/F7 (depending on the granularity you want) and keep an eye on the array in a Watch window.
That should get you going.

Why do I get access violations when a control's class name is very, very long?

I subclassed a control in order so I can add a few fields that I need, but now when I create it at runtime I get an Access Violation. Unfortunately this Access Violation doesn't happen at the place where I'm creating the control, and even those I'm building with all debug options enabled (including "Build with debug DCU's") the stack trace doesn't help me at all!
In my attempt to reproduce the error I tried creating a console application, but apparently this error only shows up in a Forms application, and only if my control is actually shown on a form!
Here are the steps to reproduce the error. Create a new VCL Forms application, drop a single button, double-click to create the OnClick handler and write this:
type TWinControl<T,K,W> = class(TWinControl);
procedure TForm3.Button1Click(Sender: TObject);
begin
with TWinControl<TWinControl, TWinControl, TWinControl>.Create(Self) do
begin
Parent := Self;
end;
end;
This successively generates the Access Violation, every time I tried. Only tested this on Delphi 2010 as that's the only version I've got on this computer.
The questions would be:
Is this a known bug in Delphi's Generics?
Is there a workaround for this?
Edit
Here's the link to the QC report: http://qc.embarcadero.com/wc/qcmain.aspx?d=112101
First of all, this has nothing to do with generics, but is a lot more likely to manifest when generics are being used. It turns out there's a buffer overflow bug in TControl.CreateParams. If you look at the code, you'll notice it fills a TCreateParams structure, and especially important, it fills the TCreateParams.WinClassName with the name of the current class (the ClassName). Unfortunately WinClassName is a fixed length buffer of only 64 char's, but that needs to include the NULL-terminator; so effectively a 64 char long ClassName will overflow that buffer!
It can be tested with this code:
TLongWinControlClassName4567890123456789012345678901234567891234 = class(TWinControl)
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
with TLongWinControlClassName4567890123456789012345678901234567891234.Create(Self) do
begin
Parent := Self;
end;
end;
That class name is exactly 64 characters long. Make it one character shorter and the error goes away!
This is a lot more likely to happen when using generics because of the way Delphi constructs the ClassName: it includes the unit name where the parameter type is declared, plus a dot, then the name of the parameter type. For example, the TWinControl<TWinControl, TWinControl, TWinControl> class has the following ClassName:
TWinControl<Controls.TWinControl,Controls.TWinControl,Controls.TWinControl>
That's 75 characters long, over the 63 limit.
Workaround
I adopted a simple error message from the potentially-error-generating class. Something like this, from the constructor:
constructor TWinControl<T, K, W>.Create(aOwner: TComponent);
begin
{$IFOPT D+}
if Length(ClassName) > 63 then raise Exception.Create('The resulting ClassName is too long: ' + ClassName);
{$ENDIF}
inherited;
end;
At least this shows a decent error message that one can immediately act upon.
Later Edit, True Workaround
The previous solution (raising an error) works fine for a non-generic class that has a realy-realy long name; One would very likely be able to shorten it, make it 63 chars or less. That's not the case with generic types: I ran into this problem with a TWinControl descendant that took 2 type parameters, so it was of the form:
TMyControlName<Type1, Type2>
The gnerate ClassName for a concrete type based on this generic type takes the form:
TMyControlName<UnitName1.Type1,UnitName2.Type2>
so it includes 5 identifiers (2x unit identifier + 3x type identifier) + 5 symbols (<.,.>); The average length of those 5 identifiers need to be less then 12 chars each, or else the total length is over 63: 5x12+5 = 65. Using only 11-12 characters per identifier is very little and goes against best practices (ie: use long descriptive names because keystrokes are free!). Again, in my case, I simply couldn't make my identifiers that short.
Considering how shortening the ClassName is not always possible, I figured I'd attempt removing the cause of the problem (the buffer overflow). Unfortunately that's very difficult because the error originates from TWinControl.CreateParams, at the bottom of the CreateParams hierarchy. We can't NOT call inherited because CreateParams is used all along the inheritance chain to build the window creation parameters. Not calling it would require duplicating all the code in the base TWinControl.CreateParams PLUS all the code in intermediary classes; It would also not be very portable, since any of that code might change with future versions of the VCL (or future version of 3rd party controls we might be subclassing).
The following solution doesn't stop TWinControl.CreateParams from overflowing the buffer, but makes it harmless and then (when the inherited call returns) fixes the problem. I'm using a new record (so I have control over the layout) that includes the original TCreateParams but pads it with lots of space for TWinControl.CreateParams to overflow into. TWinControl.CreateParams overflows all it wants, I then read the complete text and make it so it fits the original bounds of the record also making sure the resulting shortened name is reasonably likely to be unique. I'm including the a HASH of the original ClassName in the WndName to help with the uniqueness issue:
type
TWrappedCreateParamsRecord = record
Orignial: TCreateParams;
SpaceForCreateParamsToSafelyOverflow: array[0..2047] of Char;
end;
procedure TExtraExtraLongWinControlDescendantClassName_0123456789_0123456789_0123456789_0123456789.CreateParams(var Params: TCreateParams);
var Wrapp: TWrappedCreateParamsRecord;
Hashcode: Integer;
HashStr: string;
begin
// Do I need to take special care?
if Length(ClassName) >= Length(Params.WinClassName) then
begin
// Letting the code go through will cause an Access Violation because of the
// Buffer Overflow in TWinControl.CreateParams; Yet we do need to let the
// inherited call go through, or else parent classes don't get the chance
// to manipulate the Params structure. Since we can't fix the root cause (we
// can't stop TWinControl.CreateParams from overflowing), let's make sure the
// overflow will be harmless.
ZeroMemory(#Wrapp, SizeOf(Wrapp));
Move(Params, Wrapp.Orignial, SizeOf(TCreateParams));
// Call the original CreateParams; It'll still overflow, but it'll probably be hurmless since we just
// padded the orginal data structure with a substantial ammount of space.
inherited CreateParams(Wrapp.Orignial);
// The data needs to move back into the "Params" structure, but before we can do that
// we should FIX the overflown buffer. We can't simply trunc it to 64, and we don't want
// the overhead of keeping track of all the variants of this class we might encounter.
// Note: Think of GENERIC classes, where you write this code once, but there might
// be many-many different ClassNames at runtime!
//
// My idea is to FIX this by keeping as much of the original name as possible, but
// including the HASH value of the full name into the window name; If the HASH function
// is any good then the resulting name as a very high probability of being Unique. We'll
// use the default Hash function used for Delphi's generics.
HashCode := TEqualityComparer<string>.Default.GetHashCode(PChar(#Wrapp.Orignial.WinClassName));
HashStr := IntToHex(HashCode, 8);
Move(HashStr[1], Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)-8], 8*SizeOf(Char));
Wrapp.Orignial.WinClassName[High(Wrapp.Orignial.WinClassName)] := #0;
// Move the TCreateParams record back were we've got it from
Move(Wrapp.Orignial, Params, SizeOf(TCreateParams));
end
else
inherited;
end;

Richedit style formatting changes on its own

(Someone edit the title if you can understand and define my problem better.)
The problem which I am having is with style formatting of a RichEdit "reverting" back to the default "nothing" aka [] and then back to whatever I set it to, bold or italic for example.
The thing that is at fault - I assume, since I have no idea how it is breaking things - is a procedure (REMainLinesCheck) that checks for amount of lines in the RichEdit and deletes the first one until a certain point is reached (to show a maximum of 14 lines at once) like so:
while REMain.Lines.Count > 14 do
REMain.Lines.Delete(0);
I have 6 occurrences of the above procedure in other procedures that add lines to the RichEdit, but none of them change RichEdit.SelAttributes.Style but one, which was adding only one Bold line like so:
REMain.SelAttributes.Style := [fsBold];
REMain.Lines.Add('something');
REMainLinesCheck;
So I have removed all occurrences except that one and started poking around, it didn't take long to see that it was working in fact fine, regular and bold lines where being added normally and excess lines where being deleted - no problems. But as soon as I reintroduced REMainLinesCheck procedure into another procedure (for clarity purposes, lets call it Proc3Lines, because that's what it does: adds 3 lines and then calls the check for excess lines), every line that follows this Proc3Lines that should be Bold is not... From what I have experienced here it seems that REMainLinesCheck does something in Proc3Lines, since without it everything is fine.
Obviously it's not a circle of procedures that call each other, but the other parts of the code have nothing to do with this RichEdit, not to mention that I don't change RichEdit.SelAttributes.Style anywhere for REMain except that one place that I have shown, there is another RichEdit in the same unit that I do change its line's style like that, but that cannot possibly be related in any way... could it? (No it does not, I just checked.)
Basically: what the hell Delphi? It cannot get any simpler than this and I am still managing to fail, can someone explain and/or fix this? Ask questions, I'll elaborate as much as I can if something is not clear.
To apply a format to a new added line, use the following:
procedure TForm1.Button1Click(Sender: TObject);
var
LineIndex: Integer;
begin
LineIndex := RichEdit1.Lines.Add('Something');
RichEdit1.SelStart := RichEdit1.Perform(EM_LINEINDEX, LineIndex, 0);
RichEdit1.SelLength := RichEdit1.Perform(EM_LINELENGTH, RichEdit1.SelStart, 0);
RichEdit1.SelAttributes.Style := [fsBold];
end;
This has worked for me:
procedure TformStart.Proc;
var
endtxtpos: integer;
begin
endtxtpos := Length(REMain.Text);
REMain.Lines.Add('something');
REMain.SelStart := endtxtpos-(REMain.Lines.Count-1);
REMain.SelLength := Length('something');
REMain.SelAttributes.Style := [fsBold];
end;
But since I don't know any better, please criticize and suggest how I can do it better.

Am I restricted to the cursor numbers defined in Controls.pas under Delphi 7?

I am using Delphi 7 under Windows 7 to download files.
I want to change the cursor during the download.
I set the Screen.Cursor := crHourGlass; , but , after looking at the constant cursor numbers in Controls.pas, I was wondering whether there are other numbers I can use to change the cursor into ( I don't want to add a cursor to my resource file, I just want to use standard numbers which I can use without having to add resources ).
does other numbers produce meaning
full cursors
No. Other numbers besides built-in cursors constants will produce a default cursor which is identical to TCursor(crDefault) (in other terms - HCURSOR(Screen.Cursors[crDefault])). These built-in cursors resides in the application resources and preloaded on VCL startup. To add custom cursor you HAVE to add CURSOR resource and then load it and add it to VCL.
procedure TForm1.FormCreate(Sender: TObject); platform;
const
crCustom = 42;
var
Cursor: HCURSOR;
begin
Cursor := LoadCursor(HInstance, 'CUSTOM');
Win32Check(Cursor <> 0); // required error check
Screen.Cursors[crCustom] := Cursor;
{ Done, newly added crCustom is ready to use }
Self.Cursor := crCustom; // for example - lets show custom cursor
{ also, TScreen object will manage resource handle }
{ and perform cleanup for you, so DestroyCursor call is unnecessary }
end;
More complicated example with indirect cursor construction NB: example have numerous flaws: 1) DestroyIcon call is erroneous 2) they'd noticed it if there was error checking after all API calls
The "standard numbers" (crHourglass, crDefault, etc) are the predefined cursors that are provided by Delphi's VCL. You can define your own and load them into the application from a resource or from file via the Windows API, but there are no magic unpublished TCursor definitions (or stray numbers) that will mean anything. Trying to set Screen.Cursors[] to an unknown number without first loading a cursor will cause an array out of bounds error at minimum, and an access violation at the worst result in the default cursor being displayed (see TScreen.GetCursors in Forms.pas).
Quick explanation: TCursorRec is defined in the VCL source as a record containing a pointer to the next record, an index, and a cursor handle (HCURSOR). It's basically a singly-linked list, and when you ask for a cursor by accessing the Cursors list, the VCL looks through the list starting at the first item and sequentially stepping through until it either finds an index that matches the one you requested (at which point it sets the cursor to that item's HCURSOR value), or determines that the index you requested isn't assigned, in which case it returns the default cursor.
crHourGlass is of type TCursor, which is an integer alias (more or less). It is an Index that can be used to set a cursor from stock.
You can add cursors using
Screen.Cursors[Number] := ... needs to be a HCURSOR.
So if you have a handle to a new cursor, you can use that in Delphi.
Note that the crXXX constants an the TCursor type are defined in Controls and the Screen class is defined in Forms. So you can see the code for yourself.

Delphi Copy Memo to Richedit problem

I am having a problem copying the contents of a memo to a richedit component.
I thought it would be
Richedit.text := memo.text;
However if I use this the Richedit starts a new line when the memo text wraps to a new a new line (not CR/LF) but just wrapping. The richedit also starts a new line when the memo starts a new line which is fine.
Anyone got any idea's how to copy the text from a memo into the richeditbox without the lines breaking in the Richedit when the memo text wraps
Thanks
Colin
When I do
RichEdit1.Text := Memo1.Text
the virtual "line-breaks" of the Memo1 are not magically converted to line-breaks (CRLF) in the RichEdit, and they shouldn't be. These "line-breaks" are not stored in the memo text buffer. Indeed, the official Embarcadero documentation states
Set WordWrap to true to make the edit control wrap text at the right margin so it fits in the client area. The wrapping is cosmetic only. The text does not include any return characters that were not explicitly entered.
Anyhow, an alternative way is to do
RichEdit1.Lines.Assign(Memo1.Lines);
although this will preserve the virtual line-breaks, as commented below.
Update
Most likely you have some other strangeness (bug) in your code, or you have phrased your question in a too vague manner. However, to eliminate the risk of any problem with the VCL wrappers, try this:
procedure TForm4.FormClick(Sender: TObject);
var
buf: PChar;
const
MAX_BUF_SIZE = 65536;
begin
GetMem(buf, MAX_BUF_SIZE * sizeof(char));
Memo1.Perform(WM_GETTEXT, MAX_BUF_SIZE, buf);
RichEdit1.Perform(WM_SETTEXT, 0, buf);
FreeMem(buf);
end;
As a dirty hack, could you switch off word wrap on your memo then do the assignment and then switch word wrap back on? It's a nasty hack but it might do the trick for you if there is some odd behaviour.

Resources