I am using wxWidgets 3.0.2 in a static unicode build on Windows 10. I am using a wxStyledTextCtrl, which is a near 1-to-1 mapping of Scintilla.
I am looking for functionality similar to Notepad++ where upon double-clicking on something in the editor, all occurrences of that item get highlighted. It is hard to find good examples that really demonstrate styling. I've looked at wxWidgets documentation, Scintilla documentation, Notepad++ source and Code::Blocks source (the latter two use Scintilla as their text editors) and still haven't had much luck.
I've tried many different variations of the following code and it never quite works right. Either nothing is highlighted or the whole document is highlighted. I know I'm missing something, but I can't figure out what.
//textarea is a wxStyledTextCtrl*
textarea->StyleSetBackground(styleHightlightAllSelected, wxColor(80, 255, 80));
wxString selectedText = textarea->GetSelectedText();
int selSize = selectedText.size();
int selStart = textarea->GetSelectionStart();
int pos = 0;
int curr = 0;
int maxPos = textarea->GetLastPosition();
while(pos != -1){
pos = textarea->FindText(curr, maxPos, selectedText);
if(pos == selStart){ //skip the actual highlighted item
curr = pos + selSize;
} else if(pos != -1){
textarea->StartStyling(pos, 0x1F);
textarea->SetStyling(selSize, styleHightlightAllSelected);
curr = pos + selSize;
}
}
The search part of the loop does successfully find the selected text; it's just that the styling doesn't seem to take hold.
So my questions that I couldn't really find answers to are:
styleHightlightAllSelected is an int set to 100. When I had it as 0, the whole document turned green when doubleclicking. I see that styles 32-39 are predefined. Are there other styles that are predefined-but-not-really-documented; meaning, is 100 ok?
Do I have to set the entire style up, or can I just set the background color as I do above?
Is it enough to do StartStyling() and SetStyling() when I find an occurrence and be done with it, or is there more?
StartStyling() in wxWidgets has a mask argument, but the Scintilla counterpart does not. I can't clearly determine what I should set this to. It seems to be 31 (00011111) to preserve the 5 existing styling/lexer bits? Essentially, I'm not sure what to set this to if all I want to do is modify the background color of each occurrence.
My program will regularly deal with files that are dozens or more megabytes in size, so should I just be highlighting occurrences that are visible, and adjust as necessary when srolling/jumping? At the moment it searches and (fails to) set styling on each occurrence, and it takes about a second on a 50MB file. I've observed that on the same file loaded in Notepad++, it happens instantly, so I'm assuming it does it on a visible basis?
I ended up asking about this on the github issues page for the Notepad++ project, and the correct way to do this is to not use styles, but rather use indicators instead. So my code above changes to this:
int maxPos = textarea->GetLastPosition();
textarea->IndicatorClearRange(0, maxPos);
textarea->IndicatorSetStyle(styleHightlightAllSelected, wxSTC_INDIC_ROUNDBOX);
textarea->IndicatorSetAlpha(styleHightlightAllSelected, 100);
textarea->IndicatorSetUnder(styleHightlightAllSelected, true);
textarea->IndicatorSetForeground(styleHightlightAllSelected, wxColor(0, 255, 0));
wxString selectedText = textarea->GetSelectedText();
int selSize = selectedText.size();
int selStart = textarea->GetSelectionStart();
int pos = 0;
int curr = 0;
vector<int> selectionList;
while((pos = textarea->FindText(curr, maxPos, selectedText)) != -1){
selectionList.push_back(pos);
curr = pos + selSize;
}
textarea->SetIndicatorCurrent(styleHightlightAllSelected);
for(unsigned int i = 0; i < selectionList.size(); i++){
if(selectionList[i] != selStart){
textarea->IndicatorFillRange(selectionList[i], selSize);
}
}
This doesn't factor in, however, only highlighting the visible range and only highlighting new occurrences as they scroll into view (I will add this later), so for files that are dozens of megabytes in size, it will take 2-3 seconds for the highlighting to finish.
Related
I am trying to make a function that converts a TCHAR array to individual arrays, splitting it by a comma, then add a null terminator character (I think that is the name, it is '\0' in regEx). (Basically, the .split function in java, + a '\0'), but the code is only adding one value, then stops. It is very weird, as I believe this worked ~3-4 months ago, but not now for a reason unknown.
I would imagine it is an issue with my conceptual understanding of C++, as it is very much not my language of choice.
My apologies that this is basically asking someone to "fix my homework" (not actually homework, just a side project). However, I can't find the issue and have been unable to find the error for a few hours now, again probably due to me being dumb with C++.
Code Sample:
TCHAR** schemeValueToArray(TCHAR* buffer, DWORD size) {
TCHAR delim = ',';
TCHAR** out = new TCHAR*[CURSORS_AMOUNT];
int lastSeperator = 0;
for (int outOn = 0; outOn < CURSORS_AMOUNT-1; outOn++) {
// Get rest of section until end
TCHAR* temp = new TCHAR[size];
for (int i = 0; i < size; i++) {
if (buffer[i + lastSeperator] == ',') {
temp[i] = '\0';
out[outOn] = temp;
lastSeperator += i;
lastSeperator++;
break;
}
temp[i] = buffer[i + lastSeperator];
}
}
return out;
}
If you would like a bigger snippet, I can provide it.
Also, if it helps, this project once would change my mouse cursor theme when an app called AutoDarkMode requested. Making my cursor light theme during the day and dark theme at night. It would do this by copying the Registry theme value, splitting it, then putting the correct files in the correct registry spots. It was a small thing that I really liked, and now in explicitly, it has stopped.
Also, also, I am a lot more familiar with Java and Kotlin. If you are looking for ways to describe something and can relate it to something in those languages, I should be able to understand.
Thank you guys, appreciate any help!
If at all possible, I'd switch to using something like an std::vector<std::string> instead of pointers to pointers and such.
std::vector<std::string> schemeValueToArray(TCHAR* buffer) {
std::istringstream buf(buffer);
std::vector<std::string> ret;
std::string item;
while (std::getline(buf, item, ',')) {
ret.push_back(item);
}
return ret;
}
UPDATE
I'll add some info about the problem to give you a better idea about why is everything done the way it is.
The main point of the whole script is to find all errors in a special file that keeps original and translated strings.
The script requires the "special" bilingual file(an xml in real life) and a "special" vocabulary file which keeps words and their tranlations(xls, xlsx constructed by hand. PO would probably be better.)
As a result it find all errors in translation, using the provided vocabulary.
Obviously if the vocab is bad the result sucks.
At some point of time the whole thing used 'std' or mostly 'std' and 'boost regular expressions'.
At some other point of time came the need for utf-8 support, including the regular expressions. We had no time to write complex stuff, so it was decided to go the QT way.
We were aware that it is possible to iterate over bytes. But we needed actual letters and sequences of letters also we needed to cut the word ending which is done though regular expressions, and no other regex supports utf-8 relatively good.
It was decided that Qt fitted the role far better than anything we would write ourselves in very limited time, as Qt has utf-8 support, and as of v5 keeps all internal stings as utf-8 encoded(as far as I am aware).
It was pointed out that complexity of proposed solution looks like O(m * n).
In reality it's probably even worse - closer to O(m * n * log(l)) or even O(m * n * l) strait. Here m is number of strings, n - number of vocabulary records, l - number of synonyms each word has(l is always at least equals 1).
Since we need to check all strings, and for each string run the whole vocabulary to find all errors, I currently see no way how can we make it any faster, because there is no real way faster.
As the question implies I am looking for a better solution to an existing coding problem.
I am gonna try to explain what exactly the problem is as best as I can.
Imagine you have a piece of code written on C++ that takes a string, a translation of the string,
gets rid of pesky word endings.
After that it takes another file which is a vocabulary and actually runs the whole vocab to find out whether the translation of the string has any errors.
Obviously this thing is highly dependent on the actual vocabulary, but that is not really a problem.
I actually have a described piece of code, although I need to mention the whole thing runs through CGI(don't ask, but at some point it was decided that C++ will run it faster). I can have the full code uploaded to git repo, it's rather big, but I will share the essential parts here.
The current problem I am facing is two fold: either the code does not find all it is supposed to, or it works too slow(probably gets stuck somewhere, but I have not yet pin pointed where)
The main idea behind the code was:
// All definitions for essential structures so you have a better idea what he hell is goind on
struct Word {
QString full = "";
QString stemmed = "";
};
struct VocRecord {
QVector<Word> orig;
QVector<Word> trans;
QString error = "";
void clearRecord() {
this->orig.clear();
this->trans.clear();
this->error = "";
}
};
typedef QVector<VocRecord> Vocabluary;
......
Vocabluary voc = .....; // Obviosly here we get the vocabulary, now how we get it is rather complicated, you can just assume it looks like defined vector of records.
QString origStemmed, transStemmed, orig, trans;
// orig - original string
// trans - it's translation
// origStemmed - original string with removed word endings (we call it stemming hence stemmed)
// transStemmed - transtalion with removed word endings.
At first the algo was something along the lines of:
origStemmed = QString(" ") + origStemmed + QString(" "); // Add whitespaces in the begin and end of string for searching
transStemmed = QString(" ") + transStemmed + QString(" ");
for(int i = 0; i < voc.length(); i++) {
VocRecord record = voc[i];
for(int j = 0; j < record.orig.length(); j++) {
Word origWord = record.orig[j];
si = origStemmed.indexOf(QString(" ") + origWord.stemmed + QString(" "));
if(si > -1) {
int ind = origWord.stemmed.indexOf(" ");
int idx = 0;
if(ind != -1) {
// Found a space in record, means record contains at least two words.
// Here we care where the firs word ends, an it's part of the global problem
idx = origMod.indexOf(origWord.full.mid(0, ind));
} else {
// We did not find a space, do one word only, take the whole thing.
idx = origMod.indexOf(origWord.full);
}
// Now comes the tricky part, we try to figure out if that original text, in which we found our voc record, had any punctuation after the word.
// Now this actually matters only for records that have more then one word in reality, but as you'll see we check all of them and that is not correct - still figuring how to get around it.
QChar symb; - // We'll keep our last symbol of first word here
// originMod - modified original: everything is lowercase, punctuation is kept.
// The main reason we have this at all is because when stemming we have to get rid of all punctuation so we keep the "lowercased" string separate.
// I am 100% sure we don't need it at all since Qt supporrts case insensitive search, but I would like to hear your opinion on it.
if(origMod.indexOf(" ", idx) > 0) {
symb = origMod[origMod.indexOf(" ", idx)-1];
} else {
symb = origMod[origMod.length()-1];
}
// When we have the last symbol we skip the the found word
if(ind != -1 && (symb == QChar(',') || symb == QChar(';') || symb == QChar('!') || symb == QChar(':') || symb == QChar('?') || symb == QChar('.'))) {
continue;
}
// The important part ends here
............
As you will notice we search for stemmed word in the original string.
by all accounts it should work, but the main problem of proposed search that it can have several matches including false ones, and we only care about first found one. The most obvious solution is probably go through all matches, but I am unsure that is a good idea, it requires another loop and the algo is quite slow already.
The next solution I came up with to solving the problem was using regular expressions, but I must have messed up, because the algo started to be "really slow".
The main idea of the second solution:
// We DO not add spaces! spaces suck big time.
for(int i = 0; i < voc.length(); i++) {
VocRecord record = voc[i];
for(int j = 0; j < record.orig.length(); j++) {
Word origWord = record.orig[j];
// In stead of using spaces, we search for a regular expression made from vocab record.
// The simple contains actually runs into the same set of problems namely more then one match or in some cases false matches(when the searched part matches something it should not).
// Now this is terribly slow as you can imagine because we create regular expressions on the fly and not pre-make them. But I still have not thought of a way around it.
if(origStemmed.contains(origWord.stemmed + "\\b",
QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::CaseInsensitiveOption))) {
// Here we do something ungodly. We take our stemmed voc record, split it by space, then go through all parts making striing that will become our regular expression later
QString temp;
parts.clear();
parts = origWord.stemmed.split(" ");
for(int k = 0; k < parts.count(); k++) {
temp += "\\b" + parts[k] + "[a-z]*?\\b";
}
// After we added everything we need? we join the whole thing back by spaces.
temp = parts.join(" ");
// And here is the Ungodly chech - we actually search for the made regular expression in the original sting, and because we made sure to exclude any punctuation from expression in theory this should work.
if(!origMod.contains(QRegularExpression(temp, QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::CaseInsensitiveOption))) {
continue;
}
// Well it does not work, or rather it works so slow - it's impossible to get any result, and even if we do, we still don't find everything we should - I blame the shitty regex here.
// And the important part ends.
As I pointed the second solution sucks big time. Currently I am aiming for some intermediate solution and would gladly accept any tips or suggestions you can make on where to look or what to look for.
If any of you will want to see the full code for this thing - just add a comment, I'll github all the important files in a separate repo.
To whom it may concern,
GetTextExtentPoint and GetTextExtentPoint32 is giving me a bad day.
They are the only method MSDN offers for measuring text but they have two flaws, but nobody else seems to have these problems.
Firstly, they dont take into account newline. They treat the measurement as one long one liner.
Secondly and most importantly, they are causing aliasing when I do DrawText(). I am using ClearType for HFONT but still get aliasing text being drawn.
Kindly let me know precisely what is the problem. Or I might have to create my own measuring text function.
EDIT_________
// note font is created with CLEAR_TYPE_QUALITY
// so it should be antialiased
HFONT createFont(const char *face_name,int height)
{
return CreateFont(height,cHeight,0,0,0,FW_NORMAL,false,false,false,0,0,0,CLEAR_TYPE_QUALITY,0,face_name);
}
RECT box{0,0,640,480);
POINT pos{};
HFONT font = createFont("MyFavouriteFont",30);
HDC canvas = CreateCompatableDC(NULL);
HBITMAP bmp = CreateCompatibleBitmap(NULL,640,480);
SelectBitmap(canvas,bmp);
SelectFont(canvas,font);
SelectBrush(canvas,GetStockObject(DC_BRUSH));
SetDCBrushColor(RGB(255,255,255));
SetBkMode(TRANSPARENT);
SIZE measureText_Msdn(const char *s,HDC font)
{
SIZE sz;
GetTextExtentPoint(font,s,strlen(s),&sz);
return sz;
}
SIZE measureText_Custom(const char *s,HDC font)
{
SIZE sz;
TEXTMETRICSA metrics;
INT char_width, line_width;
// get char height
GetTextMetrics(font,&metrics);
while(*it)
{
if(*it == '\n' || *it == '\r')
{
if(line_width > sz.cx) sz.cx = line_width; // sz.cx stores max width
sz.cy += metrics.tmHeight;
line_width = 0;
}
else
{
GetCharWidth32(font,*it,*it,&char_width);
line_width += char_width;
}
++it;
}
if(line_width > sz.cx) sz.cx = line_width;
if(line_width > 0) sz.cy += metrics.tmHeight; // If there are no chars on this line, the line has no size
return sz;
}
void drawText(HDC dest_ctx)
{
auto s = ,"Text will look blocky";
measureText_Msdn(s,font);
// or measureText_Custom(s,font); will cause font to look blocky and ugly
// If you comment out measureText_* text will be drawn smooth.
FillRect(canvas, &box,(HBRUSH)GetCurrentObject(canvas,OBJ_BRUSH));
DrawTextA(canvas,s,-1,&box,DT_LEFT);
BitBlt(dest_ctx,pos.x,pos.y,box.right,box.bottom,
canvas,0,0,SRCCOPY);
}
SOLUTION_____
I have posted a solution as an answer. I dont like to have to do it. As I said earlier, nobody else seems to have this problem so nobody else would need the solution.
As I said earlier nobody seems to get blocky text when using any of the text measuring functions. I get it when using any of them.
What I noticed is that calling these functions require a context, and the functions seems to be currupting the context. Even if I select a new font into the context I still get blocky text output.
Thus I create a context on the fly and add the desired font into it. And measure with that context, collect the result and delete that context. I tested this and my text are drawn smooth as my original context is unaffected.
Reasons I hate this method:
- I am afraid of the expense of creating and deleting context on the fly.
- I dont think GetTextExtent should currupt my context in the first place.
If you guys have a better way, or know my problem, or why I shouldnt be having this problem kindly post.
EDIT_________
After looking at using keyboard tutorial on MSDN, I saw where the author using a window context instead of memory context for calls to GetTextExtent. It appears that GetTextExtent and the other measurement functions are only meant to be used with window contexts. Which is perfectly fine for me as I dont like Creating Memory Context anyhow.
Iam trying to highlight some words inside a PDF, I searched on a good C++ library for doing this, I found MuPDF, I download the last version and compiled it.
Now iam starting to write some codes to highlight the text in the PDF, there is no examples for this task in c++, so I start to try myself.
fz_document *doc;
fz_context *ctx;
ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
fz_register_document_handlers(ctx);
doc = fz_open_document(ctx, "D:/b.pdf");
cout << fz_count_pages(ctx, doc) << endl;
fz_page *page = fz_load_page(ctx, doc, 0);
fz_quad *q;
fz_search_page(ctx, page, "more", q, 1);
fz_rect rec = fz_rect_from_quad((*q));
fz_stext_page *pp = fz_new_stext_page(ctx, rec);
fz_point point;
point.x = 0;
point.y = 0;
fz_highlight_selection(ctx, pp, point, point, q, 16);
fz_buffer *buffer = fz_new_buffer_from_stext_page(ctx, pp);
fz_save_buffer(ctx, buffer, "D:/Final.pdf");
That is what i tried so far iam not sure it crash at a point, iam using it with Qt 5.13 MSVC 2017, so what i did wrong, or if some one has a good useful example to do this or for the library in general as it leak examples so far from my search, all the examples are in python, java, and other put for c++ there is few examples just 2 examples coming with the library.
Even if there is another good c++ library has this function please share it.
Thanks in advance.
So it seems you are making the common newbie error of thinking that just because an API uses a pointer, you must declare a pointer. But that is not correct, instead you should declare an object and pass the address of that object. So for example this
fz_quad *q;
fz_search_page(ctx, page, "more", q, 1);
fz_rect rec = fz_rect_from_quad((*q));
should actually be this
fz_quad q; // object not pointer
fz_search_page(ctx, page, "more", &q, 1); // address of the object to get the pointer
fz_rect rec = fz_rect_from_quad(q);
The idea is that fz_search_page will fill in the fz_quad object. Your version fails because you gave an uninitialised pointer to fz_search_page which will result in memory corruption when fz_search_page tries to use that pointer.
You should also definitely add the sanity check
doc = fz_open_document(ctx, "D:/b.pdf");
if (doc == nullptr) // check if we can open the document
{
std::cerr << "cannot open document\n"; // or whatever error handling you prefer
exit(1);
}
Opening files or documents can fail for all sorts of reasons and you should always check that it works.
There's probably lots else that needs improving but those issues stood out for me.
To elaborate on john's answer, you need to allocate space for the results, not just a pointer. This can be stack allocated.
fz_quad q[100]; // stack allocate array of 100 quads
int n = fz_search_page(ctx, page, "more", q, 100);
However, there seem to be some more areas of confusion as to what the APIs actually do.
The fz_search_page function returns a list of quads covering the search hits. fz_highlight_selection also returns a list of quads but this time based on the location on a page a user has dragged using the start and end coordinates of the selection.
fz_new_buffer_from_stext_page returns a plain text version of the structured text data. This is NOT in PDF format.
If you want to add a highlight annotation, then you should create a highlight annotation covering the area you want highlighted:
n = pdf_search_page(ctx, page, "more", q, 100);
if (n > 0) {
pdf_annot *annot = pdf_create_annot(ctx, page, PDF_ANNOT_HIGHLIGHT);
for (i = 0; i < n; ++i)
pdf_add_annot_quad_point(ctx, annot, q[i]);
pdf_update_annot(ctx, annot);
}
Then you can save the new modified PDF document:
pdf_save_document(ctx, doc, "out.pdf", NULL);
This should be pretty easy but I'm having a heck of a time doing it. Basically I want to move a row in my wxListCtrl up or down. I posted this to wxwidgets forum and got the following code.
m_list->Freeze();
wxListItem item;
item.SetId(item_id); // the one which is selected
m_list->GetItem(item); // Retrieve the item
m_list->DeleteItem(item_id); // Remove it
item.SetId(item_id - 1); // Move it up
m_list->SetItem(item); // Apply it's new pos in the list
m_list->Thaw();
which doesn't work. The element is deleted but not moved up (I guess the setitem line is not working). Then I thought to just switch the text and the image but I can't even get the text from the row reliably. I have
int index = m_right->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
wxString label = m_right->GetItemText(index);
if(index == 0)
return;
wxListItem item;
item.SetId(index);
bool success = m_right->GetItem(item);
wxString text = item.GetText();
but text is blank even though there is text and the index is correct. So, I'm stuck not even being able to do the most basic task. Anybody know how to do this? The code runs in a button callback (the user presses a little up arrow and my code executes to try to move it). I'm using 2.9.1 on windows.
I made it work like this with wxWidgets 2.9.3 :
void FileSelectionPanel::OnMoveUp( wxCommandEvent& WXUNUSED(evt) )
{
int idx = _listCtrl->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
if( idx == 0) idx = _listCtrl->GetNextItem( 0, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
_listCtrl->Freeze();
while( idx > -1 ) {
wxListItem item;
item.SetId(idx); _listCtrl->GetItem(item);
item.SetId(idx-1); _listCtrl->InsertItem(item);
_listCtrl->SetItemData( idx-1, _listCtrl->GetItemData( idx+1 ));
for( int i = 0; i < _listCtrl->GetColumnCount(); i++ ) {
_listCtrl->SetItem( idx-1, i, _listCtrl->GetItemText( idx+1, i ));
}
_listCtrl->DeleteItem( idx + 1 );
idx = _listCtrl->GetNextItem( idx-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
}
_listCtrl->Thaw();
}
The thing I noticed it that wxListItem is more of a convenience struct, for storing state of the view and help pass values into the wxListCtrl "nicely". It is in no way bound to what is actually inside of the wxListCtrl.
Hope this still helps anyone !
Even there is already an answer that is checked. I have the same problem here, but my list is unordered. By looking into wxWidgets' code I found out there is another important information inside the wxListItem object - the mask. I got my reordering to work correctly by setting the mask value to -1, which means that all data shall be copied. This includes the item text as well as other information, like the item data (which was important in my case).
wxListItem item;
item.SetId(item_id); // set needed id
item.SetMask(-1); // set needed data
m_list->GetItem(item); // actually retrieve the item
m_list->DeleteItem(item_id); // remove old copy
item.SetId(item_id - 1); // move item up
m_list->InsertItem(item); // insert copy of item
I also had to use "InsertItem" instead of "SetItem". Otherwise, there was no new item inserted, but an existing one overwritten (see also tomcat31's answer).
Is the list ordered? if it is auto ordering it may be ignoring the order you are trying to apply.
From recollection the internal order was not necessarily sequential, you might have to get the index of the previous item and go one before it.