I'm creating a search features in an existing c++ CodeGear project.
When you double-click a word, the background of all occurrences of the word is paint in green like in notepad++.
Before color is applied, I'm saving the original TRichEDit text in a TMemoryStream to be able to get the original text back. I reset the color back to normal on click event in the TRichEdit.
I would like to know if there is a way to save each occurrence of the search word in a TMemoryStream or maybe by using a Message like EM_STREAMOUT?
Right now everything works fine, but when the TRichEdit text is too big, it takes forever to reload the big memo of all the text. I think it would be better to only remember the color of the word that changed rather then reload all the text.
I'm really a beginner in programming, any help is appreciate. Tell me if it's not clear enough.
Here is my code that is working and putting background color to occurrences of word:
`
void SearchInText::searchWordInText(TRichEdit* reTextToSearch, AnsiString strWordToFind)
{
lstOccurrences->Clear(); //reset lst
strTextToParse = AnsiReplaceText(strTextToParse, "\r\n", "\n");
int nPrevTagPos = 0;
int nTagPos = strTextToParse.AnsiPos(strWordToFind);
while (nTagPos != 0)
{
int nPosMin = nPrevTagPos + nTagPos - 1;
//List of all the occurrence in the TRichEdit with their position in the text
//It's not a list of int, but it POINT to adresses of INT so it's the same result =)
lstOccurrences->Add((TObject*) nPosMin);
//Change color of background when there is an occurrence
changeBgColor(reTextToSearch, strWordToFind, nPosMin +1, 155, 255, 155); //lime
bColorWasApplied = true;
nPrevTagPos = nPosMin + strWordToFind.Length();
strTextToParse = strTextToParse.SubString(nTagPos + strWordToFind.Length(), strTextToParse.Length());
nTagPos = strTextToParse.AnsiPos(strWordToFind);
}
}
`
Try something like this:
#include <vector>
struct WordOccurrence
{
CHARRANGE Range;
CHARFORMAT2 OriginalFormat;
};
std::vector<WordOccurrence> WordOccurrences;
void TMyForm::HighlightWords(const String &WordToFind)
{
// disable the RichEdit's notification messages
int OriginalEventMask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);
// disable the RichEdit's painting
RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);
// save the RichEdit's current selection
CHARRANGE OriginalSelection;
RichEdit1->Perform(EM_EXGETSEL, 0, (LPARAM)&OriginalSelection);
// assign values to use while searching
int WordLen = WordToFind.Length();
int TextLen = RichEdit1->GetTextLen();
TSearchTypes SearchTypes = TSearchTypes() << stWholeWord << stMatchCase;
// find the first occurrence of the word
int StartPos = RichEdit1->FindText(WordToFind, 0, TextLen, SearchTypes);
while (StartPos != -1)
{
WordOccurrence Occurrence;
Occurrence.Range.cpMin = StartPos;
Occurrence.Range.cpMax = StartPos + WordLen;
// select the word
RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&Occurrence.Range);
// get the word's current formatting
Occurrence.OriginalFormat.cbSize = sizeof(CHARFORMAT2);
RichEdit1->Perform(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&Occurrence.OriginalFormat);
// save it for later
WordOccurrences.push_back(Occurrence);
// set the word's new formatting
CHARFORMAT2 NewFormat = Occurrence.OriginalFormat;
NewFormat.dwMask |= (CFM_COLOR | CFM_BACKCOLOR);
NewFormat.crTextColor = ...;
newFormat.crBackColor = ...;
RichEdit1->Perform(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&NewFormat);
// find the next occurrence of the word
StartPos = RichEdit1->FindText(WordToFind, Occurrence.Range.cpMax, TextLen - Occurence.Range.cpMax, SearchTypes);
}
// restore the RichEdit's original selection
RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&OriginalSelection);
// re-enable the RichEdit's painting
RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
RichEdit1->Invalidate();
// re-enable the RichEdit's notification messages
RichEdit1->Perform(EM_SETEVENTMASK, 0, OriginalEventMask);
}
void TMyForm::RestoreHighlightedWords()
{
// are there any occurrences to restore?
if (WordOccurances.empty())
return;
// disable the RichEdit's notification messages
int OriginalEventMask = RichEdit1->Perform(EM_SETEVENTMASK, 0, 0);
// disable the RichEdit's painting
RichEdit1->Perform(WM_SETREDRAW, FALSE, 0);
// save the RichEdit's current selection
CHARRANGE OriginalSelection;
RichEdit1->Perform(EM_EXGETSEL, 0, (LPARAM)&OriginalSelection);
// restore the formatting of each occurrence
for (std::vector<WordOccurrence>::iterator iter = WordOccurrences.begin();
iter != WordOccurrences.end();
++iter)
{
WordOccurrence &occurrence = *iter;
// select the word
RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&occurrence.Range);
// restore the word's original formatting
RichEdit1->Perform(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&occurrence.OriginalFormat);
}
// clear the list
WordOccurances.clear();
// restore the RichEdit's original selection
RichEdit1->Perform(EM_EXSETSEL, 0, (LPARAM)&OriginalSelection);
// re-enable the RichEdit's painting
RichEdit1->Perform(WM_SETREDRAW, TRUE, 0);
RichEdit1->Invalidate();
// re-enable the RichEdit's notification messages
RichEdit1->Perform(EM_SETEVENTMASK, 0, OriginalEventMask);
}
Ok, so I finally get it!
I add a struct in my .h
Inside it, I store:
-the position in the TRichEdit of the word found (nStart)
-the length of the word (nLength)
-the actual text and his RTF
struct sSelectedWord : public TObject
{
public:
__fastcall ~sSelectedWord(); //destructor
int nStart;
int nLength;
TMemoryStream* memoRTF;
};
Here is the code that save the RTF of my TRichEdit in my struct I just created in the .h.
void SearchInText::searchWordInText(TRichEdit* reTextToSearch, AnsiString strWordToFind)
{
lstOccurrences->Clear(); //reset lst
lstOccWithRTFMemo->Clear();
nCountOccurrence = 0;
strTextToParse = AnsiReplaceText(strTextToParse, "\r\n", "\n");
int nPrevTagPos = 0;
int nTagPos = strTextToParse.AnsiPos(strWordToFind);
while (nTagPos != 0)
{
int nPosMin = nPrevTagPos + nTagPos - 1;
//List of all the occurrence in the TRichEdit with their position in the text
//It's not a list of int, but it POINT to adresses of INT so it's the same result =)
lstOccurrences->Add((TObject*) nPosMin);
nCountOccurrence++;
//selected the word in the TRichEdit to save it with is RTF
reTextToSearch->SelStart = nPosMin;
reTextToSearch->SelLength = strWordToFind.Length();
TMemoryStream* memo = new TMemoryStream;
//important part!
rtfSaveStream(reTextToSearch,memo);
sSelectedWord* currentWord = new sSelectedWord;
currentWord->nStart = nPosMin;
currentWord->nLength = strWordToFind.Length();
currentWord->memoRTF = memo;
//Here we go, we add our new object in a list to be able to loop through it when we will want to reset the color
lstOccWithRTFMemo->Add(currentWord);
lstOccWithRTFMemo->Count;
//Change color of background when there is an occurrence
changeBgColor(reTextToSearch, strWordToFind, nPosMin +1, 155, 255, 155); //lime
bColorWasApplied = true;
nPrevTagPos = nPosMin + strWordToFind.Length();
strTextToParse = strTextToParse.SubString(nTagPos + strWordToFind.Length(), strTextToParse.Length());
nTagPos = strTextToParse.AnsiPos(strWordToFind);
}
}
The important part was done with the EM_STREAMOUT message.
void SearchInText::rtfSaveStream(TRichEdit* re, TMemoryStream* memo)
{
// Create an instance of an EDITSTREAM that will contain:
// - The detail of our callback (StreamInCallback)
// - The TMemoryStream that contains the text to paste
EDITSTREAM es = {0};
ZeroMemory(&es, sizeof(es));
es.dwCookie = (DWORD_PTR) memo;
es.dwError = 0;
es.pfnCallback = &StreamSaveInCallback ; //pointer to function callBack
//To save the selected word of the TRichEdit, use STREAMOUT
re->Perform(EM_STREAMOUT, SF_RTF | SFF_SELECTION, (LPARAM)&es);
}
DWORD CALLBACK SearchInText::StreamSaveInCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
{
TMemoryStream *memo = (TMemoryStream*)dwCookie;
memo->Write(pbBuff, cb);
*pcb = cb;
return 0;
}
In the pbuff, you can see the word that you selected in your TRichEdit with his RTF!
Hope it will help others with the same issue! Thanks for those who suggested code =)
Related
I have a tiff file and would like to get a list of all tags used in that file. If I understand the TiffGetField() function correctly, it only gets the values of tags specified. But how do I know what tags the file uses? I would like to get all used tags in the file. Is there an easy way to get them with libtiff?
It seems to be a very manual process from my experience. I used the TIFF tag reference here https://www.awaresystems.be/imaging/tiff/tifftags.html to create a custom structure
typedef struct
{
TIFF_TAGS_BASELINE Baseline;
TIFF_TAGS_EXTENSION Extension;
TIFF_TAGS_PRIVATE Private;
} TIFF_TAGS;
With each substructure custom defined. For example,
typedef struct
{
TIFF_UINT32_T NewSubfileType; // TIFFTAG_SUBFILETYPE
TIFF_UINT16_T SubfileType; // TIFFTAG_OSUBFILETYPE
TIFF_UINT32_T ImageWidth; // TIFFTAG_IMAGEWIDTH
TIFF_UINT32_T ImageLength; // TIFFTAG_IMAGELENGTH
TIFF_UINT16_T BitsPerSample; // TIFFTAG_BITSPERSAMPLE
...
char *Copyright; // TIFFTAG_COPYRIGHT
} TIFF_TAGS_BASELINE;
Then I have custom readers:
TIFF_TAGS *read_tiff_tags(char *filename)
{
TIFF_TAGS *tags = NULL;
TIFF *tif = TIFFOpen(filename, "r");
if (tif)
{
tags = calloc(1, sizeof(TIFF_TAGS));
read_tiff_tags_baseline(tif, tags);
read_tiff_tags_extension(tif, tags);
read_tiff_tags_private(tif, tags);
TIFFClose(tif);
}
return tags;
}
Where you have to manually read each field. Depending on if it's an array, you'll have to check the return status. For simple fields, it's something like
// The number of columns in the image, i.e., the number of pixels per row.
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &tags->Baseline.ImageWidth);
but for array fields you'll need something like this
// The scanner model name or number.
status = TIFFGetField(tif, TIFFTAG_MODEL, &infobuf);
if (status)
{
len = strlen(infobuf);
tags->Baseline.Model = malloc(sizeof(char) * (len + 1));
_mysprintf(tags->Baseline.Model, (int)(len + 1), "%s", infobuf);
tags->Baseline.Model[len] = 0;
}
else
{
tags->Baseline.Model = NULL;
}
// For each strip, the byte offset of that strip.
status = TIFFGetField(tif, TIFFTAG_STRIPOFFSETS, &arraybuf);
if (status)
{
tags->Baseline.NumberOfStrips = TIFFNumberOfStrips(tif);
tags->Baseline.StripOffsets = calloc(tags->Baseline.NumberOfStrips, sizeof(TIFF_UINT32_T));
for (strip = 0; strip < tags->Baseline.NumberOfStrips; strip++)
{
tags->Baseline.StripOffsets[strip] = arraybuf[strip];
}
}
else
{
tags->Baseline.StripOffsets = NULL;
}
My suggestion is to only read the fields you want/need and ignore everything else. Hope that helps.
I need to change the voice of the Text To Speech engine. When a menu is selected (ID_SPEAK_PLAY), I get the text of an edit box and simply read it.
My situation can be solved in two ways :
insert the XML code at the begining of ptrData without using strncat or other functions that involve creating other wchar_t* buffers (memory issues ). StringCchPrintf is not working.
change the voice in some other way that i don't know.
Here is my code :
case ID_SPEAK_PLAY:
text_size = SendMessage(h_edit, WM_GETTEXTLENGTH, 0, 0);
text_size += 100;
ptrData = new wchar_t[text_size];
SendMessage(h_edit, WM_GETTEXT, text_size, (LPARAM)ptrData);
StringCchPrintf(ptrData, text_size, L"<voice required = \"Gender=Female;Age=Teen\"> %s", ptrData);
pVoice->Speak(ptrData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] ptrData;
break;
StringCchPrintf is not working.
That is because you ignored the warning in the documentation:
Behavior is undefined if the strings pointed to by pszDest, pszFormat, or any argument strings overlap.
You are specifying ptrData as both pszDest and an argument string, so your code has undefined behavior. You must use separate buffers when using StringCchPrintf():
case ID_SPEAK_PLAY:
text_size = SendMessage(h_edit, WM_GETTEXTLENGTHW, 0, 0) + 1;
ptrData = new wchar_t[text_size];
SendMessage(h_edit, WM_GETTEXTW, text_size, (LPARAM)ptrData);
speak_size = text_size + 100;
speakData = new wchar_t[speak_size];
StringCchPrintf(speakData, speak_size, L"<voice required = \"Gender=Female;Age=Teen\"> %s", ptrData);
pVoice->Speak(speakData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] speakData;
delete [] ptrData;
break;
Alternatively, just skip StringCchPrintf() and let WM_GETTEXT populate your single buffer directly:
case ID_SPEAK_PLAY:
{
const wchar_t *xml = L"<voice required = \"Gender=Female;Age=Teen\"> ";
const int xml_size = lstrlenW(xml);
text_size = SendMessage(h_edit, WM_GETTEXTLENGTHW, 0, 0);
ptrData = new wchar_t[text_size + xml_size + 1];
lstrcpyW(ptrData, xml);
SendMessage(h_edit, WM_GETTEXTW, text_size+1, (LPARAM)(ptrData+xml_size));
pVoice->Speak(ptrData, SPF_ASYNC | SPF_IS_XML, NULL);
delete [] ptrData;
break;
}
change the voice in some other way that i don't know.
Instead of inserting XML in front of your text, you can call the ISpVoice::SetVoice() method before calling ISpVoice::Speak(). Use SpEnumTokens() to know which voices are installed, or use SpFindBestToken() to search for a voice that matches the criteria you need.
I am trying to implement a syntax highlight editor with a richedit, it has works well with the current selected line, but I may be missing something. The CRichEdit is my own wrapper implementation of the richedit controller, the problem seems that the text is not selected properly even though I made sure the selected range generated with the code were what I get with the EM_EXGETSEL message.
The selection seems increasing 1 as the lines goes down, so I decided to ed_source.sendMessage(EM_LINEFROMCHAR, pos, 0) to the range which partially fix the problem except for some few lines where the coloring seems some time one or to position before and the real appropriate, so that is why I thing I may not be understanding something.
void parse(WIN::CRichEdit &ed_source, bool curseline)
{
int pos, offset = 0;
char delimiter[]={" \n\r(){};"}, *tok, *start;
CStringA s;
CString text;
CWnd api;
if(curseline){
ed_source.getLine(ed_source.getRow() - 1, text);
offset = ed_source.sendMessage(EM_LINEINDEX, -1, 0);
}else{
text = ed_source.getCaption();
}
s = text;
start = s.c_str();
if(!start) return;
tok = strtok(s.c_str(), delimiter);
CHARRANGE cr = ed_source.getSelecteRange();
ed_source.sendMessage(EM_HIDESELECTION, 1, 0) ;
CHARRANGE range;
while(tok)
{
int len = strlen(tok);
pos = (tok - start);
int x = ed_source.sendMessage(EM_LINEFROMCHAR, pos, 0);
range.cpMin = offset + pos - x;
range.cpMax = range.cpMin + len;
ed_source.selectRange(range);
if(isReserved(tok)){
ed_source.setTextStyle(true, false);
ed_source.setTextColor(keyboardColor);
}else
if(isType(tok)){
ed_source.setTextStyle(false, false);
ed_source.setTextColor(typeColor);
}else {
ed_source.setTextStyle(false, true);
ed_source.setTextColor(textColor);
}
tok = strtok(0, delimiter);
}
ed_source.sendMessage(EM_HIDESELECTION, 0, 0) ;
ed_source.selectRange(cr);
}
just to be more specific the moment I call the above function is immediately after loading the text on it. I assumed you may want to see the implementation of some of the above functions so here they are.
CHARRANGE CRichEdit::getSelecteRange()
{
CHARRANGE crg = {0} ;
sendMessage(EM_EXGETSEL, 0, (LPARAM)&crg);
return crg;
}
void CRichEdit::selectRange(const CHARRANGE &cr)
{
sendMessage( EM_EXSETSEL, 0, (LPARAM) &cr);
}
void CRichEdit::setTextColor(COLORREF col)
{
CHARFORMAT format;
memset(&format, 0, sizeof(CHARFORMAT));
format.cbSize = sizeof(CHARFORMAT);
format.dwMask = CFM_COLOR;
format.crTextColor = col;
sendMessage( EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &format);
}
Have a look at this article for some ideas:
Faster rich edit syntax highlighting
http://bcbjournal.org/articles/vol3/9910/Faster_rich_edit_syntax_highlighting.htm
It was written for the TRichEdit control in C++Builder, but the majority of the tips in it use straight Win32 API calls, and the few places that use VCL idioms can be easily adapted into Win32 equivilents.
Update: Try removing EM_LINEFROMCHAR from your loop. offset + pos is already an absolute character position within the RichEdit, there should be no need to adjust it on each loop iteration. If you really want to take line indexes into account then you should be looping through the lines one row at a time parsing each row individually, not parsing the entire content as a single string. Try something more like this instead:
void parse(WIN::CRichEdit &ed_source, bool curseline)
{
int startLine, endLine, offset;
const char* delimiters = " \n\r(){};";
char *tok, *start;
CStringA s;
CWnd api;
if (curseline)
{
startLine = ed_source.getRow() - 1;
endLine = startLine + 1;
}
else
{
startLine = 0;
endLine = ed_source.sendMessage(EM_GETLINECOUNT, 0, 0);
}
CHARRANGE cr = ed_source.getSelecteRange();
int eventMask = ed_source.SendMessage(EM_SETEVENTMASK, 0, 0);
ed_source.SendMessage(WM_SETREDRAW, FALSE, 0);
for (int line = startLine; line < endLine; ++line)
{
CString text;
ed_source.getLine(line, text);
s = text;
start = s.c_str();
if (!start) continue;
offset = ed_source.sendMessage(EM_LINEINDEX, line, 0);
tok = strtok(start, delimiters);
while (tok)
{
CHARRANGE range;
range.cpMin = offset + (int)(tok - start);
range.cpMax = range.cpMin + strlen(tok);
ed_source.selectRange(range);
if (isReserved(tok))
{
ed_source.setTextStyle(true, false);
ed_source.setTextColor(keyboardColor);
}
else if (isType(tok))
{
ed_source.setTextStyle(false, false);
ed_source.setTextColor(typeColor);
}
else
{
ed_source.setTextStyle(false, true);
ed_source.setTextColor(textColor);
}
tok = strtok(0, delimiters);
}
}
ed_source.SendMessage(WM_SETREDRAW, TRUE, 0);
ed_source.Invalidate(); // whatever your wrapper does to call ::InvalidateRect()
ed_source.SendMessage(EM_SETEVENTMASK, 0, eventMask);
ed_source.selectRange(cr);
}
With that said, instead of using getLine() and strtok() to parse text, you might consider using EM_FINDWORDBREAK to locate words and EM_EXSETSEL/EM_GETSELTEXT to retreive the characters of each word. That way, you are using less memory and letting the RichEdit do more of the searching for you. You can use EM_SETWORDBREAKPROC/EX if you want to customize the word delimiters searched for.
I have a vector that stores pairs. Each pair contains a CString and a bool.
If the CString is meant to be underlined then bool is true, else it is false.
I want to output the text in the vector to the screen making sure that text is underlined in the correct places.
So far I have the following code:
void CEmergenceView::AppendText( CString msg ) {
int nBegin;
CRichEditCtrl &rec = GetRichEditCtrl();
nBegin = rec.GetTextLength();
rec.SetSel(nBegin, nBegin); // Select last character
rec.ReplaceSel(msg); // Append, move cursor to end of text
rec.SetSel(-1,0); // Remove Black selection bars
nBegin = rec.GetTextLength(); // Get New Length
rec.SetSel(nBegin,nBegin); // Cursor to End of new text
// Fix annoying "do you want to save your changes?" when program exits
GetDocument()->SetModifiedFlag(FALSE); // -Optional- (sometimes you want this)
}
int nEnd = 0;
// loop through start of text to end of text
for(int k = 0; k < groups.size(); k++) {
rEditCtrl.SetSel(nEnd, nEnd);
rEditCtrl.GetSelectionCharFormat(cf);
if(groups.at(k).second) {
if(!cf.dwEffects & !CFE_UNDERLINE) {
CRichEditView::OnCharUnderline();
}
}
else if(!groups.at(k).second) {
if(cf.dwEffects & CFE_UNDERLINE) {
CRichEditView::OnCharUnderline();
}
}
AppendText(groups.at(k).first);
nEnd = nEnd + (groups.at(k).first.GetLength());
}
However, this is not underlining at all....Can anyone tell me what I'm doing wrong?? Thanks!
I think you should implement the OnCharUnderline
Try to call yours own function instead of the default one:
You can get it from here:
void CMyRichEditView::OnCharUnderline ()
{
CHARFORMAT2 cf;
cf = GetCharFormatSelection();
if (!(cf.dwMask & CFM_UNDERLINE) || !(cf.dwEffects & CFE_UNDERLINE))
cf.dwEffects = CFE_UNDERLINE;
else
cf.dwEffects = 0;
cf.dwMask = CFM_UNDERLINE;
SetCharFormat(cf);
}
I am writing a text editor using the wxWidgets framework. I need to get the word under caret from the text control. Here is what I came up with.
static bool IsWordBoundary(wxString& text)
{
return (text.Cmp(wxT(" ")) == 0 ||
text.Cmp(wxT('\n')) == 0 ||
text.Cmp(wxT('\t')) == 0 ||
text.Cmp(wxT('\r')) == 0);
}
static wxString GetWordUnderCaret(wxTextCtrl* control)
{
int insertion_point = control->GetInsertionPoint();
wxTextPos last_position = control->GetLastPosition();
int start_at, ends_at = 0;
// Finding starting position:
// from the current caret position, move back each character until
// we hit a word boundary.
int caret_pos = insertion_point;
start_at = caret_pos;
while (caret_pos)
{
wxString text = control->GetRange (caret_pos - 1, caret_pos);
if (IsWordBoundary (text)) {
break;
}
start_at = --caret_pos;
}
// Finding ending position:
// from the current caret position, move forward each character until
// we hit a word boundary.
caret_pos = ends_at = insertion_point;
while (caret_pos < last_position)
{
wxString text = control->GetRange (caret_pos, caret_pos + 1);
if (IsWordBoundary (text)) {
break;
}
ends_at = ++caret_pos;
}
return (control->GetRange (start_at, ends_at));
}
This code works as expected. But I am wondering is this the best way to approach the problem? Do you see any possible fixes on the above code?
Any help would be great!
Is punctuation part of a word? It is in your code -- is that what you want?
Here is how I would do it:
wxString word_boundary_marks = " \n\t\r";
wxString text_in_control = control->GetValue();
int ends_at = text_in_control.find_first_of( word_boundary_marks, insertion_point) - 1;
int start_at = text_in_control.Mid(0,insertion_point).find_last_of(word_boundary_marks) + 1;
I haven't tested this, so there likely are one or two "off-by-one" errors and you should add checks for "not found", end of string, and any other word markers. My code should give you the basis for what you need.