Gtk Move window beyond constraints - c++

I am currently writing a gtk program that uses a custom title bar (i.e., it is not being decorated by the window manager). But since using a custom title bar also disables support of dragging the window around, I wrote my custom drag function which moves the window by calling window.move(x, y):
bool on_titlebar_drag(GdkEvent* event)
{
static int drag_x_offset = 0;
static int drag_y_offset = 0;
int x, y;
if (event->type == GDK_BUTTON_PRESS)
{
drag_x_offset = event->button.x;
drag_y_offset = event->button.y;
} else if(event->type == GDK_BUTTON_RELEASE) {
drag_x_offset = 0;
drag_y_offset = 0;
} else if(event->type == GDK_MOTION_NOTIFY) {
x = event->motion.x_root - drag_x_offset;
y = event->motion.y_root - drag_y_offset;
mainWindow.move(x, y);
}
return true;
}
This works just fine except of the fact that it cannot move the window beyond the screen limits, like the normal behaviour for other windows, so you can drag it "out of sight" to make place for others.
I am trying to resize the window smaller as soon as it touches the screen by calling window.resize(width, height) but this is not what I intend to do, because resizing also resizes the window contents to the smaller scale, while I would just like to make its physical size smaller.
I have also tried using set_allocation and size_allocate, these two didnt make any change at all.
My question is, do you know a way to either be able to move the window beyond the screen borders (not totally, but in a way that the window is not fully on-screen), or to change the size of the window without resizing its contents?

If you are using GTK 3.10 or newer you can use gtk_window_set_titlebar
gtk_window_set_titlebar (GtkWindow *window, GtkWidget *titlebar) the only arguments you need are the window that you want to customise and the GtkWidget that will serve as the titlebar. Using GtkHeaderBar is suggested but in your case you can use any custom GtkWidget and get draggable bar which will tug the whole window as would the one from the window manager.

Related

wxStyledTextCtrl - Size of AutoComp

I was just wondering if it is possible to find the size (in pixels) of the autocompletion control shown by the wxStyledTextCtrl.
My goal is to show a help window associated with the entry when a selection happens. Therefore, I need the location and also the width of the autocompletion control. It seems location can be found from m_STC->AutoCompPosStart() but there seems to be no way of finding the width. I am using the following code:
auto StartPos = m_STC->ToPhys(m_STC->PointFromPosition(m_STC->AutoCompPosStart()));
int MaxChars = m_STC->AutoCompGetMaxWidth(); //returns 0 unless set to a fixed value
int w, h;
m_STC->GetTextExtent(wxString("A", MaxChars), &w, &h);
return wxPoint(StartPos.x + w, StartPos.y);
I am using Windows and wxWidgets 3.2.
There is no way to get this information from the styled text control because the autocomp window is completely managed by Scintilla. And unfortunately, Scintilla doesn't make any methods available for getting this info.
As a hack-around, the popup is currently implemented as a child window of the styled text control. So you could do something like this:
const wxWindowList& childred = m_stc->GetChildren();
for ( auto it = childred.begin() ; it != childred.end() ; ++it )
{
// We're assuming the styled text control has at most 1 child -
// namely the autocomp popup. It might be better to check that
// the window found is in fact the auto comp popup somehow.
// win->GetPosition() will return screen coordinates, so to get client
// coordinates, ScreenToClient must be called.
wxPoint psn = m_stc->ScreenToClient(win->GetPosition());
wxSize sz = win->GetSize();
// Do something with size and position here.
}
However, this isn't guaranteed to always work. If in the future, the auto comp popup implementation is changed to use a top level window instead of a child of the control, this method will fail.

Notifying wxSizer of dynamic layout changes

I have the following hierarchy for layout:
wxMDIChildFrame -> wxNotebook ->
wxScrolledWindow -> wxBoxSizer -> wxStyledTextCtrl
GOAL: The wxStyledTextCtrl (CScriptWnd) resizes itself when user adds or deletes a line or presses Shift+Enter to add another CScriptWnd to the wxScrolledWindow.
The following code is when a line is added:
void CScriptWnd::OnNewLineAdded(wxStyledTextEvent& event)
{
long col, line;
PositionToXY(GetInsertionPoint(), &col, &line);
auto ClientSize = GetClientSize();
int Height = ClientSize.GetHeight();
Height += TextHeight(line);
SetClientSize(ClientSize.GetWidth(), Height);
SetMinSize(wxSize(ClientSize.GetWidth(), Height));
Refresh();
//m_ParentWindow->FitInside(); //CScriptWnd gets smaller rather than larger
//m_ParentWindow->Layout(); //CScriptWnd gets smaller rather than larger
//m_ParentWindow->GetGrandParent()->Layout(); //No effect
event.Skip();
}
Most work well such as the CScriptWnd resizes itself, the new script window added to wxScrolledWindow etc...
The problem is that the update to user interface only properly happens when the wxMDIChildFrame is resized using the mouse.
For example, if there are two CScriptWnd and the top one resizes itself, it overlaps with the bottom one
until the wxMDIChildFrame is resized using the mouse. Similar happens for the visibility of scrollbars such that when CScriptWnd client size gets larger the scrollbars only become visible when top-level window resized using the mouse.
Not sure what I am missing.
You probably need
SetMinSize(GetSize());
GetParent()->Layout();

How to draw buttons in TStringGrid cells

I am trying to customize a TStringGrid by adding visual objects to cells within the grid. One column needs to contain standard windows push buttons in each row and another column needs to contain a drop down with pre-defined options.
From what I have read the best way to achieve this is to draw the buttons manually in the OnDrawCell event handler. All of the examples I have found use DrawFrameControl() which does not draw a themed button like you would expect in Windows 7 or later.
Is there an equivalent function to DrawFrameControl() that will allow me to draw a themed button and if so can someone please give an example of how I might use it?
I also tried creating a vector of TButtons and setting the parent of each button to be the StringGrid and placing each button within the relevant cell. This also works but does now allow for scrolling of the grid when there are more cells that can be displayed visible area.
I am using RAD Studio 10.2 C++ builder and using the BCC32C compiler (clang-enhanced). It is a VCL WIN32 application.
Is there an equivalent function to DrawFrameControl() that will allow me to draw a themed button
The Win32 DrawFrameControl() function is for drawing non-themed UI controls. To draw themed UI controls, you need to use the Win32 Theming functions instead - DrawThemeBackground(), DrawThemeEdge(), DrawThemeText(), etc. These functions are wrapped for you by the VCL's Vcl.Themes unit. In particular, use the TThemeServices class, which has various Draw...() methods that you can use when TThemeServices.Available and TThemeServices.Enabled are both true.
I also tried creating a vector of TButtons and setting the parent of each button to be the StringGrid and placing each button within the relevant cell. This also works but does now allow for scrolling of the grid when there are more cells that can be displayed visible area.
Correct. You would have to subclass the StringGrid to intercept the scrolling so you can reposition the buttons manually.
Just for completeness here is the code I got working in order to draw a Windows Themed button in a TStringGrid cell:
void __fastcall TForm_Controller::StringGrid1DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
TStringGrid *grid;
bool ButtonDown = false, ButtonHot = false, ButtonInFocus = false;
TThemedElementDetails LDetails;
TTextFormatFlags LTextFormat;
TColor LColor, TempColor;
TCustomStyleServices *LStyle;
int XPos, YPos;
TPoint points[3];
grid = (TStringGrid *)Sender;
//a cell with a button ('+' or '-')
if((ACol == 0) && (ARow > 0) && grid->Cells[ACol][ARow].Length())
{
Rect.Left -= 4; //override padding so button fills entire cell
if ((FocusCell.X == ACol) && (FocusCell.Y == ARow))
ButtonHot = true;
if ((CellDown.X == ACol) && (CellDown.Y == ARow))
ButtonDown = true;
LStyle = StyleServices();
if (LStyle->Enabled && LStyle->Available)
{
if (ButtonDown)
LDetails = LStyle->GetElementDetails(tbPushButtonPressed);
else if (ButtonHot)
LDetails = LStyle->GetElementDetails(tbPushButtonHot);
else if (ButtonInFocus)
LDetails = LStyle->GetElementDetails(tbPushButtonDefaulted);
else
LDetails = LStyle->GetElementDetails(tbPushButtonNormal);
LStyle->DrawElement(grid->Canvas->Handle, LDetails, Rect);
if (LStyle->GetElementColor(LDetails, ecTextColor, LColor))
grid->Canvas->Font->Color = LColor;
grid->Canvas->Font->Size = 13;
LTextFormat = (tfCenter);
LStyle->DrawTextA(grid->Canvas->Handle, LDetails, grid->Cells[ACol][ARow], Rect, TTextFormat() << tfCenter << tfVerticalCenter);
}
else //themed drawing not available so revert to old style
DrawButtonFace(grid->Canvas, Rect, 1, bsAutoDetect, true, ButtonDown, ButtonInFocus);
}
}
I then use the OnMouseDown, OnMouseMove, OnMouseLeave and OnMouseUp events to determine what state the buttons need to be in using 3 TPoint objects FocusCell, PrevCell, CellDown.

Some controls are not drawing, seemingly at random

I'm trying to write a little MFC app just for myself, to test some AI's I'm training.
So I added a picture control and a static control where I can paint stuff freely in the OnPaint() method of my main Window.
It seems to work when just drawing my app once, but I now added a loop that performs OnPaint() multiple times before stopping.
When in this loop, some other controls don't show up, for example all my buttons are gone, and some sliders even are missing some times, but other times, they're there.
My code goes like this:
void CKiUebung1Dlg::OnBnClickedButtongo()
{
m_bisGoing = true;
OnPaint();
if(m_fDiagramData.size() <= 0)
{
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
}
OnPaint();
for(int i(9); i >= 0; --i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
OnPaint();
}
m_bisGoing = false;
OnPaint();
}
void CKiUebung1Dlg::OnPaint()
{
if(IsIconic())
{
CPaintDC dc(this); // Gerätekontext zum Zeichnen
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Symbol in Clientrechteck zentrieren
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Symbol zeichnen
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
{
constexpr const int border = 5;
CPaintDC dc(&m_cDiagram);
CRect l_cPos;
m_cDiagram.GetClientRect(&l_cPos);
const int width(l_cPos.Width() - border * 2 - 2), height(l_cPos.Height() - border * 2 - 12);
const int numPoints(m_fDiagramData.size());
POINT* points(new POINT[numPoints]);
for(int i(numPoints - 1); i >= 0; --i)
{
const int
x((float)i / (numPoints - 1) * width + border + 1),
y(height - m_fDiagramData[i] * height + border + 9);
points[i] = { x,y };
}
dc.Polyline(points, numPoints);
static CString going(_T(" "));
if(m_bisGoing) { going += _T("."); if(going.GetLength() > 300) going = _T(" ."); }
else going = _T(" ");
float fprog(0); if(m_fDiagramData.size() > 0) fprog = m_fDiagramData.back();
CString prog; prog.Format(_T("Progress %03.2f%%"), fprog * 100); if(m_bisGoing) prog += going;
m_cDiagram.SetWindowTextW(prog);
m_cDiagram.RedrawWindow();
delete[] points;
}
}
This is how it looks when the loop isn't running:
This is how it looks when the loop is running:
You seem to have trouble understanding how invalidating/painting works.
The documentation you should read first is:
Painting and Drawing
While many developers recommend painting only in WM_PAINT processing (OnPaint() in MFC), this is not always the best solution, because this message is low-priority, painting may not be immediate (have a "choppy" feel), and you may get a "flickering" effect.
Instead, I sometimes recommend a mix of drawing and painting:
Employ painting in WM_PAINT processing. This should paint the whole client area (or only the invalidated part of it, if you want a more "optimized" implementation). Please note that WM_PAINT message may be received as a result of invalidating a part or all of the client area, due to moving, resizing, unhiding etc the window, in addition to programmatically invalidating it. So in response to a WM_PAINT message you should perform a full repaint, ie all the items you want to be displayed.
Employ drawing for the changes you want to be shown immediately, while the application is busy (not waiting for the "asynchronous" WM_PAINT message to be received). Please note that these should be in WM_PAINT processing as well, so you rather have to write some drawing/painting routines, taking a HDC (or CDC*) as a parameter (along any other parameter needed), and call them from both the OnPaint() function (passing the ClientDC there) and from your additional drawing actions needed (passing a CDC* acquired by calling GetDC()).
So, let me share my experience with an application I wrote some (long) time ago. It's an image-display/manipulation (among others) application, processing images in a custom format, and using a special library, which was rather "slow", as it only provided a function to display the image in the device context (this includes possible cropping, adjustments, resizing etc which are CPU-costly operations). Here is an image:
You can see the user performing a selection. The application has to display the image, and possibly the selection rectangle on top of it, and of course that's what OnPaint() does. An "easy" (albeit technically "correct") implementation would be to call Invalidate() or InvalidateRect() in response each mouse move message (while selecting). This would cause a full repaint (which is "OK"), but also suffer from performance problems, due to the slow image-library: if you also call UpdateWindow() after invalidating (requesting an immediate refresh) performance would be sluggish (having to reprocess/redisplay the image), if not, the refresh would just take place some (noticeable) time later. This was solved by employing drawign (not painting) in response to the WM_MOUSEMOVE message: no invalidating there, instead drawing just the selection rectangle (after restoring the part modified by the previous selection message - I only backup/restore the four sides of the frame, not the whole rectangle). As a result, the application is responsive and the operation smooth, despite the slow library, and shows the image and the selection correctly, even if you switch to another application and then back to it, while the selection is being tracked (dashed line).
Some notes and suggestion about your implementation (it has quite a few issues):
As other members have noted, you may not call OnPaint() yourself. Especially those calls after Invalidate() make absolutely no sense. Instead, call UpdateWindow(), if you want an immediate update.
Imo it is NOT OK to perform calculations within OnPaint(), and I mean those points calculations (although in your case the calculation is rather trivial). OnPaint() should just display the data calculated in another part of your code.
Also, setting the m_cDiagram text and repainting from within OnPaint() is not OK either (may cause additional paint requests). Better move these into OnBnClickedButtongo().
You don't need to invalidate (and particularly erase) the whole client area to cause some controls to be repainted, instead invalidate only those controls. Remember, the sleep_for() function is blocking, and the WM_PAINT message won't be sent and processed while your loop is running.
Btw, consider a non-blocking approach, eg using a timer, as #Barmak Shemirani suggested. Alternatively, it may be possible to write a "non-blocing sleep()" by running the message-loop yourself (take parts of the code in CWinApp::Run() and modify it).
Since you have a dialog and created separate controls to display your data, using OnPaint() is not a good implementation, as it affects (paints) the whole client area. It is mostly useful for classes like CView or CScrollView (or custom-painting CWnds in general). You paint the graph on the dialog's surface, and have to perform calculations to get the coordinates in m_cDiagram (btw you can use GetWindowRect() and then ScreenToClient() instead) but it would be best to use an owner-drawn control (to paint/draw the graph on), and it's not really difficult, you just have to respond to paint requests (just as in OnPaint()), and the device context you get can paint on the control only, not on the dialog; coordinates are relative to the control's client area, starting from (0,0).
Hope this helps
CWnd::OnPaint is a response to WM_PAINT message and should not be called directly.
WM_PAINT calls CWnd::OnPaint, which calls CPaintDC dc(this), which in turns calls BeginPaint/EndPaint APIs. This sequence of message+response should be left as is.
Therefore CPaintDC dc(this) must appear once - and only once - inside OnPaint, and not anywhere else. Override OnPaint as follows:
void CMyDialog::OnPaint()
{
CDialogEx::OnPaint(); //this will call CPaintDC dc(this);
//optional:
CClientDC dc(this); //CClientDC can be used anywhere in a valid window
//use dc for drawing
}
//or
void CMyDialog::OnPaint()
{
CPaintDC dc(this);
//use dc for drawing
}
You also don't need the outdated if (IsIconic()) {...} condition.
To force the window to repaint itself, call Invalidate() (same thing as InvalidateRect(NULL, TRUE))
InvalidateRect(NULL, TRUE) is a request to repaint the window. The system will look at this request, and will send WM_PAINT message to that window when there is a chance. Therefore a call to InvalidateRect may not process the way you expect it to work in a sequential program. For example, a second consecutive call to InvalidateRect will not have any effect. Window was already marked to be updated.
for(int i(9); i >= 0; --i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
m_fDiagramData.push_back((float)rand() / RAND_MAX);
InvalidateRect(NULL, TRUE);
OnPaint();
}
OnPaint() should be removed from above code. Still, animation is not possible in a single thread (at least not in this manner). The program is busy going through the loop, it cannot deal with WM_PAINT and other messages.
So you need an additional thread, or simply use SetTimer, and respond to ON_WM_TIMER()/OnTimer for animation. Example:
int counter = 0;
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_WM_PAINT()
ON_WM_TIMER()
...
END_MESSAGE_MAP()
void CMyDialog::OnPaint()
{
CPaintDC dc(this);
CString s;
s.Format(L"%02d", counter);
dc.TextOut(0, 0, s);
}
void CMyDialog::animate()
{
counter = 0;
SetTimer(1, 1000, NULL);
}
void CMyDialog::OnTimer(UINT_PTR n)
{
if(n == 1)
{
Invalidate(); //force repaint
counter++;
if(counter == 10)
KillTimer(1);
}
}

Make a QDialog appear in a different screen

The title says it pretty much all:
I have two screens, and each time I create a QDialog it appears in the same screen as its parent.
How can I make it appear in a different screen? Or should I use a different type of top-level widget?
The code I use to create the dialog is:
QDialog my_dialog = new QDialog(this,
Qt::WindowMaximizeButtonHint |
Qt::WindowCloseButtonHint);
...
EDIT:
I have also tried using the QDesktopWidget which gives me a QScreen object that refers to the second screen. But then I don't find how to instruct the QDialog to use that QScreen (setting it as the parent doesn't work).
It is bad, that you edit your question without reading comments :(
// Your screen geometry:
QRect buildScreenGeometry()
{
auto desktop = QApplication::desktop();
QRect virtualRect;
const auto n = desktop->screenCount();
for ( auto i = 0; i < n; i++ )
virtualRect |= desktop->screenGeometry(i);
return virtualRect;
}
// Moving
auto dlg = new QDialog( someParent );
auto newPoint = QPoint( 2000, 0 ); // point on another screen
auto realPos = someParent->mapFromGlobal( newPoint );
dlg->move( realPos );
That's all.
UPDATE:
You should understand, that there are only ONE screen area with COMMON coordinate system, that contains ALL screens.
For example, you have 2 monitors with 800x600 resolution. First (main) monitor is standing left, and second standing right. In this case, coordinate system, that is available for your application is 1600x600. So, if your widget has 100x100 top-left position, on a first monitor, and you want to move it to another, you should call move(900x100); // 900 == screen1.width() + dialog.pos().x(). Then your widget will have 100x100 position on second monitor.
You should read Qt documentation.
You can use move on your QDialog, but be aware that move will set the QDialog position relative to it's parent.
You can get the Main Window's screen position and use that to setup the QDialog position. Just know that you're not guaranteed to have 2 screens on the end user machine.
For more information on move see: http://doc.qt.io/qt-5/application-windows.html#window-geometry