Multiline tooltips and CMFCButton - button

At the moment I am using a standard CButton control and my own derived CMultilineToolTipCtrl to be able to display multiline tool tips.
I thought I would try out the CMFCButton control as an alternative because it has native support for a tooltip by calling SetTooltip.
However, it does not seem to support multiline text from what I can tell. Is there a simple solution to this or should I stick with what I have coded?

This is the derived tooltip control that determines the correct width for the tool tip control:
UINT CMultilineToolTipCtrl::FitText(CString &rStrText)
{
CClientDC *pDC ;
CFont *pFntTip, *pFntOld ;
CRect rctMargin, rctTip ;
CSize sizText ;
CString strTextCopy, strLine, strChaff ;
CStringArray aryStrLines ;
UINT uMaxWidth;
INT_PTR uNumLines, uLineIndex ;
uMaxWidth = 0 ; // Fallback.
GetMargin(&rctMargin);
GetClientRect(&rctTip);
// Reduce by the given margins to get the true area.
rctTip.left += rctMargin.left ;
rctTip.top += rctMargin.top ;
rctTip.right -= rctMargin.right ;
rctTip.bottom -= rctMargin.bottom ;
pDC = new CClientDC(this);
if (pDC != nullptr)
{
pFntTip = GetFont();
pFntOld = (CFont *)pDC->SelectObject(pFntTip);
// First, find the longest line.
strTextCopy = rStrText ;
uMaxWidth = 0 ;
while (strTextCopy != _T("") )
{
strLine = strTextCopy.SpanExcluding(_T("\r\n") );
strTextCopy.Delete(0, strLine.GetLength() );
strChaff = strTextCopy.SpanIncluding(_T("\r\n") );
strTextCopy.Delete(0, strChaff.GetLength() );
sizText = pDC->GetTextExtent(strLine);
if (sizText.cx > (int)uMaxWidth)
uMaxWidth = sizText.cx ;
aryStrLines.Add(strLine);
}
rStrText = _T("");
uNumLines = aryStrLines.GetSize();
// Now, pad all lines to that max width.
for (uLineIndex = 0 ; uLineIndex < uNumLines ; uLineIndex++)
{
strLine = aryStrLines.GetAt(uLineIndex);
sizText = pDC->GetTextExtent(strLine);
while (sizText.cx <= (int)uMaxWidth)
{
strLine += _T(" ");
sizText = pDC->GetTextExtent(strLine);
}
rStrText += strLine ;
}
SendMessage(TTM_SETMAXTIPWIDTH, 0, uMaxWidth);
pDC->SelectObject(pFntOld);
delete pDC ;
}
return uMaxWidth ;
}
If you have a simpler way to calculate the width then I welcome your answers.

Related

QTextLayout::drawCursor() does not work when subclassing QAbstractTextDocumentLayout

currently QTextEdit and QPlainTextEdit does not meet my requirements, so I need to subclass QAbstractTextDocumentLayout to provide custom layout of the document. I reference QPlainTextDocumentLayout and QTextDocumentLayout a lot, and finally got a simple layout to display the text. However, I couldn't see the cursor in QTextEdit, which should be blinking. I need help to figure this out.
I am using Qt 5.9.1. The simple project is here. The draw() function of VTextDocumentLayout looks like this:
void VTextDocumentLayout::draw(QPainter *p_painter, const PaintContext &p_context)
{
qDebug() << "VTextDocumentLayout draw()" << p_context.clip << p_context.cursorPosition << p_context.selections.size();
// Find out the blocks.
int first, last;
blockRangeFromRect(p_context.clip, first, last);
if (first == -1) {
return;
}
QTextDocument *doc = document();
QPointF offset(m_margin, m_blocks[first].m_offset);
QTextBlock block = doc->findBlockByNumber(first);
QTextBlock lastBlock = doc->findBlockByNumber(last);
qreal maximumWidth = m_width;
while (block.isValid()) {
const BlockInfo &info = m_blocks[block.blockNumber()];
const QRectF &rect = info.m_rect;
QTextLayout *layout = block.layout();
if (!block.isVisible()) {
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
continue;
}
QTextBlockFormat blockFormat = block.blockFormat();
QBrush bg = blockFormat.background();
if (bg != Qt::NoBrush) {
fillBackground(p_painter, rect, bg);
}
auto selections = formatRangeFromSelection(block, p_context.selections);
QPen oldPen = p_painter->pen();
p_painter->setPen(p_context.palette.color(QPalette::Text));
layout->draw(p_painter,
offset,
selections,
p_context.clip.isValid() ? p_context.clip : QRectF());
// Draw the cursor.
int blpos = block.position();
int bllen = block.length();
bool drawCursor = p_context.cursorPosition >= blpos
&& p_context.cursorPosition < blpos + bllen;
Q_ASSERT(p_context.cursorPosition >= -1);
if (drawCursor) {
int cpos = p_context.cursorPosition;
cpos -= blpos;
qDebug() << "draw cursor" << block.blockNumber() << blpos << bllen << p_context.cursorPosition << cpos;
layout->drawCursor(p_painter, offset, cpos);
}
p_painter->setPen(oldPen);
offset.ry() += rect.height();
if (block == lastBlock) {
break;
}
block = block.next();
}
}
I called layout->drawCursor() to draw the cursor but this function seems to do nothing.
Any help is appreciated! Thanks!
Update:
Add the log as following:
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
block range 0 1
draw cursor 1 5 15 19 14
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) -1 0
block range 0 1
blockBoundingRect() 1 13 QRectF(0,0 75x17)
VTextDocumentLayout draw() QRectF(67,0 9x13) 19 0
When running this project in Linux, I coundn't see the cursor. However, when running it in Windows, I could see the cursor but not blinking.
Update:
It seems that QTextEdit pass a wrong clip rect to the layout. If I just inserted one line of text (only one block within the document), I could see the blinking cursor. Quite strange!!!
The default value of PaintContext class cursorPosition is -1.
http://doc.qt.io/qt-4.8/qabstracttextdocumentlayout-paintcontext.html#cursorPosition-var
If any default value is not provided to the cursor position ever (it is a public variable), then my guess is value remains -1, and drawCursor is always false (as the block position index minimum value is zero in worst case).
Try setting some default value to PaintContext::cursorPosition, which may display the cursor.
When subclassing QAbstractTextDocumentLayout, blockBoundingRect() should return the geometry of the block, not just the rect as what QPlainTextDocumentLayout does.
In one word, QPlainTextDocumentLayout provides a BAD example for subsclassing QAbstractTextDocumentLayout.

Why is my "for()" not working

for(xwingwaves = 0 ; xwingwaves < xwingwc ; xwingwaves += 1)
{
if(alarm[3] = -1) alarm[3] = 500;
}
my for is getting activated but my if is not getting started. Coded in gamemaker.
xwingwc = 2;
is there a var xwingwaves before the loop? I think you're just missing the declaration.
== instead of =
if(alarm[3] == -1) alarm[3] = 500;
and for(var xwingwaves = 0; instead of for(xwingwaves = 0;
GameMaker: Studio - Loops
Your code is correct, but you do strange thing.
for (xwingwaves = 0 ; xwingwaves < xwingwc ; xwingwaves += 1)
{
if(alarm[3] = -1) alarm[3] = 500;
}
Here you start alarm[3], if it isn't started.
Here you check alarm[3] multiple times. You do same thing 2 times. No reason to do this. No reason to use for loop. No any difference with simple:
if (alarm[3] = -1) alarm[3] = 500;
Keep in mind that if you haven't any code inside alarm[3] event (empty event), it won't be started (in this case just add one line od code, like // empty inside)
And keep in mind that this code starts alarm[3] insde object, where this code placed (or called from, if the code placed in script).
for(xwingwaves = 0 ; xwingwaves < xwingwc ; xwingwaves += 1)
{
if(alarm[3] <= -1) alarm[3] = 500;
}
//also i dont understand why you loop this? heres the code you need
if(alarm[3] <= -1) alarm[3] = 500; //the loop is meaningless put it in step event

pebble bitmap xor or mask?

On the Pebble watch I am trying to overwrite a bitmap layer with text so the text is written white over black areas and black over white areas.
In other environments I would do this with an XOR operation, or create a mask and perform a couple of writes after masking out what I don't want overwritten.
I don't see an XOR graphics operator or a mask operator in the Pebble graphics library.
How can this be done?
I'm using C and CloudPebble.
Lynd
Here is a routine to do it. The text layer must be 'under' the graphic layer, then you use layer_set_update_proc() to set the update procedure for the graphic layer to the routine below.
static void bitmapLayerUpdate(struct Layer *layer, GContext *ctx){
GBitmap *framebuffer;
const GBitmap *graphic = bitmap_layer_get_bitmap((BitmapLayer *)layer);
int height;
uint32_t finalBits;
uint32_t *bfr, *bitmap;
framebuffer = graphics_capture_frame_buffer(ctx);
if (framebuffer == NULL){
APP_LOG(APP_LOG_LEVEL_DEBUG, "capture frame buffer failed!!");
} else {
// APP_LOG(APP_LOG_LEVEL_DEBUG, "capture frame buffer succeeded");
}
height = graphic->bounds.size.h;
for (int yindex =0; yindex < height; yindex++){
for ( unsigned int xindex = 0; xindex < (graphic->row_size_bytes); xindex+=4){
bfr = (uint32_t*)((framebuffer->addr)+(yindex * framebuffer->row_size_bytes)+xindex);
bitmap = (uint32_t*)((graphic->addr)+(yindex * graphic->row_size_bytes)+xindex);
finalBits = *bitmap ^ *bfr;
// APP_LOG(APP_LOG_LEVEL_DEBUG, "bfr: %0x, bitmsp: %0x, finalBits: %x", (unsigned int)bfr, (unsigned int)bitmap, finalBits );
*bfr = finalBits;
}
}
graphics_release_frame_buffer(ctx, framebuffer);
}

How to read columns in qtreeWidget?

I have two columns in treewidget viz. Files and path. Now I want to dump the items into an xml file. But the issue I'm facing is that I'm unable to extract the items.
QDomDocument document;
QDomElement root = document.createElement("Playlist");
document.appendChild(root);
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Save", "Do you want to save the playlist?", MessageBox::Yes | QMessageBox::No);
if(reply == QMessageBox::Yes)
{
//Add some elements
QDomElement playlist = document.createElement("MyPlaylist");
playlist.setAttribute("Name", "Playlist1");
root.appendChild(playlist);
for(int h = 0; h < ui->treeWidget->topLevelItemCount(); h++)
{
QDomElement file = document.createElement("File");
file.setAttribute("ID", ui->treeWidget->columnAt(0)); //1
file.setAttribute("Path", ui->treeWidget->columnAt(1)); //2
playlist.appendChild(file);
}
}
Can anyone help me out how to handle such situation wherein multiple column items are to be read. Comments 1 & 2 are the essence of whole story.
I think, you can write it like:
[..]
for(int h = 0; h < ui->treeWidget->topLevelItemCount(); h++)
{
QDomElement file = document.createElement("File");
QTreeWidgetItem *item = ui->treeWidget->topLevelItem(h);
file.setAttribute("ID", item->text(0)); //1
file.setAttribute("Path", item->text(1)); //2
playlist.appendChild(file);
}

QGraphicsItem: Why no `stackAfter` method?

I'm having an annoying time trying to get around the 'recommended' way of doing something.
So, I have a stack of cards. I want to make it so that when I deal a card, it becomes the last-drawn object of the entire scene (typical bring_to_front functionality).
The recommended way to do this is just adding to the object's zValue until it is larger than all the rest, but I was hoping to do away with rather "lazy" integers running around all over the place with judicious use of the stackBefore method, which simulates reorganizing the order in which objects were added to the scene.
This works perfectly fine when I shuffle my cards in a limited set (get list of selected items, random.shuffle, for item do item.stackBefore(next item)), but it is certainly not working when it comes to bubbling the card to the top of the entire scene.
I considered adding a copy of the object to the scene and then removing the original, but it just seems like I should be able to do stackAfter like I would when using a Python list (or insertAt or something).
Sample code:
def deal_items(self):
if not self.selection_is_stack():
self.statusBar().showMessage("You must deal from a stack")
return
item_list = self.scene.sorted_selection()
for i,item in enumerate(item_list[::-1]):
width = item.boundingRect().width()
item.moveBy(width+i*width*0.6,0)
another_list = self.scene.items()[::-1]
idx = another_list.index(item)
for another_item in another_list[idx+1:]:
another_item.stackBefore(item)
This works. It just seems somewhat... ugly.
self.scene.items returns the items in the stacking order (link). So if you want to stackAfter an item, you can just query the z value of the current topmost item and then set the z value of the new topmost card to a value one larger.
item.setZValue(self.scene.items().first().zValue() + 1)
Hope that helps.
Edit added src for stackBefore and setZValue from http://gitorious.org/qt/
src/gui/graphicsview/qgraphicsitem.cpp
void QGraphicsItem::stackBefore(const QGraphicsItem *sibling)
{
if (sibling == this)
return;
if (!sibling || d_ptr->parent != sibling->parentItem()) {
qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling);
return;
}
QList<QGraphicsItem *> *siblings = d_ptr->parent
? &d_ptr->parent->d_ptr->children
: (d_ptr->scene ? &d_ptr->scene->d_func()->topLevelItems : 0);
if (!siblings) {
qWarning("QGraphicsItem::stackUnder: cannot stack under %p, which must be a sibling", sibling);
return;
}
// First, make sure that the sibling indexes have no holes. This also
// marks the children list for sorting.
if (d_ptr->parent)
d_ptr->parent->d_ptr->ensureSequentialSiblingIndex();
else
d_ptr->scene->d_func()->ensureSequentialTopLevelSiblingIndexes();
// Only move items with the same Z value, and that need moving.
int siblingIndex = sibling->d_ptr->siblingIndex;
int myIndex = d_ptr->siblingIndex;
if (myIndex >= siblingIndex) {
siblings->move(myIndex, siblingIndex);
// Fixup the insertion ordering.
for (int i = 0; i < siblings->size(); ++i) {
int &index = siblings->at(i)->d_ptr->siblingIndex;
if (i != siblingIndex && index >= siblingIndex && index <= myIndex)
++index;
}
d_ptr->siblingIndex = siblingIndex;
for (int i = 0; i < siblings->size(); ++i) {
int &index = siblings->at(i)->d_ptr->siblingIndex;
if (i != siblingIndex && index >= siblingIndex && index <= myIndex)
siblings->at(i)->d_ptr->siblingOrderChange();
}
d_ptr->siblingOrderChange();
}
}
void QGraphicsItem::setZValue(qreal z)
{
const QVariant newZVariant(itemChange(ItemZValueChange, z));
qreal newZ = newZVariant.toReal();
if (newZ == d_ptr->z)
return;
if (d_ptr->scene && d_ptr->scene->d_func()->indexMethod != QGraphicsScene::NoIndex) {
// Z Value has changed, we have to notify the index.
d_ptr->scene->d_func()->index->itemChange(this, ItemZValueChange, &newZ);
}
d_ptr->z = newZ;
if (d_ptr->parent)
d_ptr->parent->d_ptr->needSortChildren = 1;
else if (d_ptr->scene)
d_ptr->scene->d_func()->needSortTopLevelItems = 1;
if (d_ptr->scene)
d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true);
itemChange(ItemZValueHasChanged, newZVariant);
if (d_ptr->flags & ItemNegativeZStacksBehindParent)
setFlag(QGraphicsItem::ItemStacksBehindParent, z < qreal(0.0));
if (d_ptr->isObject)
emit static_cast<QGraphicsObject *>(this)->zChanged();
}

Resources