I have implemented a Xdnd drop support implementation in VTK some time ago. It was working great except with Thunar file manager. All other file managers were working fine at the time. We dismissed this limitation a Thunar bug at the time.
The feature I implemented was very simple:
Set the window of the application to be XdndAware
Receive the position message and respond that we are ready to receive
Receive the drop mesage and request a selection
Receive the selection notify and recover the URI
Convert the URI into something we can work with
Nothing fancy, I did not even touch the list type.
Fast forward a few years and now dolphin users cannot drop files correctly into our application. The URI is always the first file dropped since dolphin was started. Restarting our application has no effect. No bug at all with pcmanfm.
This is not a dolphin bug and files can be dropped on blender or firefox from dolphin without issues.
So there must be a bug in our implementation, but I've been staring at the code for some time and everything I tried had no effect, except for breaking Xdnd support completely.
Here are the interesting part of the implementation:
//------------------------------------------------------------------------------
vtkXRenderWindowInteractor::vtkXRenderWindowInteractor()
{
this->Internal = new vtkXRenderWindowInteractorInternals;
this->DisplayId = nullptr;
this->WindowId = 0;
this->KillAtom = 0;
this->XdndSource = 0;
this->XdndPositionAtom = 0;
this->XdndDropAtom = 0;
this->XdndActionCopyAtom = 0;
this->XdndStatusAtom = 0;
this->XdndFinishedAtom = 0;
}
[...]
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor::Enable()
{
// avoid cycles of calling Initialize() and Enable()
if (this->Enabled)
{
return;
}
// Add the event handler to the system.
// If we change the types of events processed by this handler, then
// we need to change the Disable() routine to match. In order for Disable()
// to work properly, both the callback function AND the client data
// passed to XtAddEventHandler and XtRemoveEventHandler must MATCH
// PERFECTLY
XSelectInput(this->DisplayId, this->WindowId,
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | ExposureMask |
StructureNotifyMask | EnterWindowMask | LeaveWindowMask | PointerMotionHintMask |
PointerMotionMask);
// Setup for capturing the window deletion
this->KillAtom = XInternAtom(this->DisplayId, "WM_DELETE_WINDOW", False);
XSetWMProtocols(this->DisplayId, this->WindowId, &this->KillAtom, 1);
// Enable drag and drop
Atom xdndAwareAtom = XInternAtom(this->DisplayId, "XdndAware", False);
char xdndVersion = 5;
XChangeProperty(this->DisplayId, this->WindowId, xdndAwareAtom, XA_ATOM, 32, PropModeReplace,
(unsigned char*)&xdndVersion, 1);
this->XdndPositionAtom = XInternAtom(this->DisplayId, "XdndPosition", False);
this->XdndDropAtom = XInternAtom(this->DisplayId, "XdndDrop", False);
this->XdndActionCopyAtom = XInternAtom(this->DisplayId, "XdndActionCopy", False);
this->XdndStatusAtom = XInternAtom(this->DisplayId, "XdndStatus", False);
this->XdndFinishedAtom = XInternAtom(this->DisplayId, "XdndFinished", False);
this->Enabled = 1;
this->Modified();
}
[...]
//------------------------------------------------------------------------------
void vtkXRenderWindowInteractor::DispatchEvent(XEvent* event)
{
int xp, yp;
switch (event->type)
{
[...]
// Selection request for drag and drop has been delivered
case SelectionNotify:
{
// Sanity checks
if (!event->xselection.property || !this->XdndSource)
{
return;
}
// Recover the dropped file
char* data = nullptr;
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
XGetWindowProperty(this->DisplayId, event->xselection.requestor, event->xselection.property,
0, LONG_MAX, False, event->xselection.target, &actualType, &actualFormat, &itemCount,
&bytesAfter, (unsigned char**)&data);
// Conversion checks
if ((event->xselection.target != AnyPropertyType && actualType != event->xselection.target) ||
itemCount == 0)
{
return;
}
// Recover filepaths from uris and invoke DropFilesEvent
std::stringstream uris(data);
std::string uri, protocol, hostname, filePath;
std::string unused0, unused1, unused2, unused3;
vtkNew<vtkStringArray> filePaths;
while (std::getline(uris, uri, '\n'))
{
if (vtksys::SystemTools::ParseURL(
uri, protocol, unused0, unused1, hostname, unused3, filePath, true))
{
if (protocol == "file" && (hostname.empty() || hostname == "localhost"))
{
// The uris can be crlf delimited, remove ending \r if any
if (filePath.back() == '\r')
{
filePath.pop_back();
}
// The extracted filepath miss the first slash
filePath.insert(0, "/");
filePaths->InsertNextValue(filePath);
}
}
}
this->InvokeEvent(vtkCommand::DropFilesEvent, filePaths);
XFree(data);
// Inform the source the the drag and drop operation was sucessfull
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = event->xclient.data.l[0];
reply.xclient.message_type = this->XdndFinishedAtom;
reply.xclient.format = 32;
reply.xclient.data.l[0] = this->WindowId;
reply.xclient.data.l[1] = itemCount;
reply.xclient.data.l[2] = this->XdndActionCopyAtom;
XSendEvent(this->DisplayId, this->XdndSource, False, NoEventMask, &reply);
XFlush(this->DisplayId);
this->XdndSource = 0;
}
break;
case ClientMessage:
{
if (event->xclient.message_type == this->XdndPositionAtom)
{
// Drag and drop event inside the window
// Recover the position
int xWindow, yWindow;
int xRoot = event->xclient.data.l[2] >> 16;
int yRoot = event->xclient.data.l[2] & 0xffff;
Window root = DefaultRootWindow(this->DisplayId);
Window child;
XTranslateCoordinates(
this->DisplayId, root, this->WindowId, xRoot, yRoot, &xWindow, &yWindow, &child);
// Convert it to VTK compatible location
double location[2];
location[0] = static_cast<double>(xWindow);
location[1] = static_cast<double>(this->Size[1] - yWindow - 1);
this->InvokeEvent(vtkCommand::UpdateDropLocationEvent, location);
// Reply that we are ready to copy the dragged data
XEvent reply;
memset(&reply, 0, sizeof(reply));
reply.type = ClientMessage;
reply.xclient.window = event->xclient.data.l[0];
reply.xclient.message_type = this->XdndStatusAtom;
reply.xclient.format = 32;
reply.xclient.data.l[0] = this->WindowId;
reply.xclient.data.l[1] = 1; // Always accept the dnd with no rectangle
reply.xclient.data.l[2] = 0; // Specify an empty rectangle
reply.xclient.data.l[3] = 0;
reply.xclient.data.l[4] = this->XdndActionCopyAtom;
XSendEvent(this->DisplayId, event->xclient.data.l[0], False, NoEventMask, &reply);
XFlush(this->DisplayId);
}
else if (event->xclient.message_type == this->XdndDropAtom)
{
// Item dropped in the window
// Store the source of the drag and drop
this->XdndSource = event->xclient.data.l[0];
// Ask for a conversion of the selection. This will trigger a SelectioNotify event later.
Atom xdndSelectionAtom = XInternAtom(this->DisplayId, "XdndSelection", False);
XConvertSelection(this->DisplayId, xdndSelectionAtom,
XInternAtom(this->DisplayId, "UTF8_STRING", False), xdndSelectionAtom, this->WindowId,
CurrentTime);
}
else if (static_cast<Atom>(event->xclient.data.l[0]) == this->KillAtom)
{
this->ExitCallback();
}
}
break;
}
}
[...]
And header:
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderingUIModule.h" // For export macro
#include <X11/Xlib.h> // Needed for X types in the public interface
class vtkCallbackCommand;
class vtkXRenderWindowInteractorInternals;
class VTKRENDERINGUI_EXPORT vtkXRenderWindowInteractor : public vtkRenderWindowInteractor
{
public:
static vtkXRenderWindowInteractor* New();
vtkTypeMacro(vtkXRenderWindowInteractor, vtkRenderWindowInteractor);
void PrintSelf(ostream& os, vtkIndent indent) override;
/**
* Initializes the event handlers without an XtAppContext. This is
* good for when you don't have a user interface, but you still
* want to have mouse interaction.
*/
void Initialize() override;
/**
* Break the event loop on 'q','e' keypress. Want more ???
*/
void TerminateApp() override;
/**
* Run the event loop and return. This is provided so that you can
* implement your own event loop but yet use the vtk event handling as
* well.
*/
void ProcessEvents() override;
///#{
/**
* Enable/Disable interactions. By default interactors are enabled when
* initialized. Initialize() must be called prior to enabling/disabling
* interaction. These methods are used when a window/widget is being
* shared by multiple renderers and interactors. This allows a "modal"
* display where one interactor is active when its data is to be displayed
* and all other interactors associated with the widget are disabled
* when their data is not displayed.
*/
void Enable() override;
void Disable() override;
///#}
/**
* Update the Size data member and set the associated RenderWindow's
* size.
*/
void UpdateSize(int, int) override;
/**
* Re-defines virtual function to get mouse position by querying X-server.
*/
void GetMousePosition(int* x, int* y) override;
void DispatchEvent(XEvent*);
protected:
vtkXRenderWindowInteractor();
~vtkXRenderWindowInteractor() override;
/**
* Update the Size data member and set the associated RenderWindow's
* size but do not resize the XWindow.
*/
void UpdateSizeNoXResize(int, int);
// Using static here to avoid destroying context when many apps are open:
static int NumAppInitialized;
Display* DisplayId;
Window WindowId;
Atom KillAtom;
int PositionBeforeStereo[2];
vtkXRenderWindowInteractorInternals* Internal;
// Drag and drop related
Window XdndSource;
Atom XdndPositionAtom;
Atom XdndDropAtom;
Atom XdndActionCopyAtom;
Atom XdndStatusAtom;
Atom XdndFinishedAtom;
///#{
/**
* X-specific internal timer methods. See the superclass for detailed
* documentation.
*/
int InternalCreateTimer(int timerId, int timerType, unsigned long duration) override;
int InternalDestroyTimer(int platformTimerId) override;
///#}
void FireTimers();
/**
* This will start up the X event loop and never return. If you
* call this method it will loop processing X events until the
* application is exited.
*/
void StartEventLoop() override;
private:
vtkXRenderWindowInteractor(const vtkXRenderWindowInteractor&) = delete;
void operator=(const vtkXRenderWindowInteractor&) = delete;
};
#endif
The complete file can be seen here:
https://gitlab.kitware.com/vtk/vtk/-/blob/master/Rendering/UI/vtkXRenderWindowInteractor.cxx
You can follow my train of thoughts and debugs here:
https://gitlab.kitware.com/f3d/f3d/-/issues/228
To test this code, a simple way is to use F3D has it is using the dropped file, but a simple VTK application should work as well:
https://gitlab.kitware.com/f3d/f3d
Current Dolphin issue
From some testing, the issue is with the preparation and sending of the XdndFinished ClientMessage back to the drag and drop source when handling the SelectionNotify event.
Instead of:
reply.xclient.window = event->xclient.data.l[0];
the line should be:
reply.xclient.window = this->XdndSource;
This aligns the XClientMessageEvent window member with the target window argument to XSendEvent. This is probably a simple copy-paste error, as xclient isn't valid for the SelectionNotify event type. Most likely the actual value of window was not previously being checked, but that has been changed recently, hence the error.
The spec covers this quite well, and also raises a couple of other things to consider:
For data.l[1]: "Bit 0 is set if the current target accepted the drop and successfully performed the accepted drop action. (new in version 5)", so using itemCount as a value will technically be incorrect whenever the count is even
If the handling of XdndPosition doesn't need to actually track where the current position is (i.e. if you are just using the entire window as a drop target), you may be able to get away with sending the XdndStatus in response to the XdndEnter message
Previous Thunar issue
Looking into this further, I did some troubleshooting regarding the previous issue with Thunar, and it boils down to the code handling XdndDrop assuming that the format of the incoming data can be converted to UTF8_STRING. This diff for GLFW handles almost the exact same issue.
If, when handling the XdndEnter message, you inspect the values of xclient.data.l[2] through xclient.data.l[4] you can see that Dolphin reports supporting the following formats:
text/uri-list
text/x-moz-url
text/plain
whereas Thunar only supports the following:
text/uri-list
The simplest solution is to:
Keep track of a supported format when handling XdndEnter
Provide this format to XConvertSelection when handling XdndDrop (instead of UTF8_STRING)
Handle the format appropriately when handling the SelectionNotify event
To be more complete, if bit 0 of xclient.data.l[1] is set on the XdndEnter message, you should get the XdndTypeList property of the drag and drop source window and based the format selection on that, instead of the formats contained in the message itself.
I have a certain selection in combo box. Based on that selection some items need to be enabled/disabled. However I am unable to do so. And also another problem is once a single option is selected I cant change it to another selection without backspace and typing that selection again.
m_d_lvlayers is the variable of IDC of combo box. Its type is CString.
void CThermalToolDlg::OnCbnSelchangeLvLayers()
{
// TODO: Add your control notification handler code here
if (m_d_lvlayers == "2" )
{
UpdateData();
GetDlgItem(IDC_LV3_CU)->EnableWindow(0);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(0);
//etc etc
UpdateData(0);
}
else if (m_d_lvlayers == "3")
{
UpdateData();
GetDlgItem(IDC_LV3_CU)->EnableWindow(1);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(1);
//etc etc
UpdateData(0);
}
else
{
UpdateData();
GetDlgItem(IDC_LV3_CU)->EnableWindow(1);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(1);
//etc etc
UpdateData(0);
}
}
I expect to get proper selections in combobox and corresponding enabling and disabling.
You need to call UpdateData(TRUE); first.
bSaveAndValidate
Flag that indicates whether dialog box is being initialized (FALSE) or data is being retrieved (TRUE)
void CThermalToolDlg::OnCbnSelchangeLvLayers()
{
UpdateData(TRUE); // Controls to Variables
if (m_d_lvlayers == "2" )
{
GetDlgItem(IDC_LV3_CU)->EnableWindow(FALSE);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(FALSE);
//etc etc
UpdateData(FALSE);
}
else if (m_d_lvlayers == "3")
{
GetDlgItem(IDC_LV3_CU)->EnableWindow(TRUE);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(TRUE);
//etc etc
UpdateData(FALSE);
}
else
{
GetDlgItem(IDC_LV3_CU)->EnableWindow(TRUE);
GetDlgItem(IDC_LV3_ICI)->EnableWindow(TRUE);
//etc etc
UpdateData(FALSE);
}
}
Although in your code it makes no sense to call UpdateData(FALSE); because all you are doing is setting the control window state to enabled.
I use edit control to display input value in MFC. That value is used in a while loop to calculate other parameters. When using EN_CHANGE to update data value, I can not use float input number. How can I actually change that value after hit "Enter" key?
float m_ini_gainG; // m_ini_gainG is value of edit Control.
void CMy0Dlg::OnClickedPlay()
{
m_thread = AfxBeginThread(MainThread,this);
}
UINT CMy01Dlg::MainThread(LPVOID pParam)
{
CMy01Dlg *pMainDlg = (CMy01BayerToRGBDlg*)pParam;
while(1)
{
pMainDlg->m_iTimer = pMainDlg->SetTimer(DISPLAY, 10, 0);
}
return 0;
}
void CMy01BayerToRGBDlg::OnTimer(UINT_PTR nIDEvent)
{
if(nIDEvent == DISPLAY)
{
Read(data); // data is read continuously
......
gainB = temp_G*m_ini_gainG/temp_B;
m_gainB.Format(L"%0.2f",gainB);
UpdateData(FALSE);
}
CDialogEx::OnTimer(nIDEvent);
}
void CMy01BayerToRGBDlg::OnChangeIniG()
{
UpdateData(TRUE);
}
I can only set integer value for m_ini_gainG. I can not enter "." to set float value.
I m trying to make a media player . The time status of the track is shown using QSlider. Track seek should happen when the user releases the slider somewhere on the QSlider. I had a look on the list of QSlider signals. The one that seems to fit is sliderReleased() which happens when the user releases the slider, but it does not get the latest value of where slider is. So to get sliders latest value I used sliderMoved() and stored reference.
Signal and slot connection
connect(this->ui->songProgress, SIGNAL(sliderMoved(int)), this,
SLOT(searchSliderMoved(int)));
connect(this->ui->songProgress, SIGNAL(sliderReleased()), this,
SLOT(searchSliderReleased()));
Functions
void MainWindow::searchSliderMoved(int search_percent)
{
value=this->ui->songProgress->value();
std::cout<<"Moved: Slider value"<<value<<std::endl;
}
Now I am Using the "value" variable inside searchSliderReleased for seeking
void MainWindow::searchSliderReleased()
{
std::cout<<"Released: Slider value"<<value<<std::endl;
emit search(value);//seek the track to location value
}
But the problem with above technique is that sliderMoved signal does not give the value where the slider is dropped but it give the location from where the slider was moved. How do I obtain the value where the slider was dropped?
You can use the valueChanged(int) signal of the slider, but set the flag
ui->horizontalSlider->setTracking(false);
Just finished building a DiectShow media player with QSlider here at work. Here's a few slot snippets on how I did it.
Widget::sliderPressed()
{
if( isPlaying() )
{
m_wasPlaying = true;
pause();
}
else
{
m_wasPlaying = false;
}
// Set a flag to denote slider drag is starting.
m_isSliderPressed = true;
}
Widget::sliderReleased()
{
if( m_wasPlaying )
{
play();
}
m_wasPlaying = false;
m_isSliderPressed = false;
}
Widget::valueChanged( int value )
{
if( m_isSliderPressed )
{
// Do seeking code here
}
else
{
// Sometime Qt when the user clicks the slider
// (no drag, just a click) Qt will signals
// pressed, released, valueChanged.
// So lets handle that here.
}
}
Had the same problem and calling sliderPosition() instead of value() directly when handling sliderReleased() worked for me.
I prefer to save the last value set by our app, like this pseudocode:
// Save our last value
lastSoftwareVal = 0;
// Set value from program
slider_set_value(value){
lastSoftwareVal = value;
slider.setValue(value)
}
// Slider on change callback
slider_on_change(value){
if(value == lastSoftwareVal){
// Value was changed by our app
return false;
}
// User has changed the value, do something
}
I have a CEdit text box which is a part of a property pane and only allows numeric values (positive integers). The box works fine when people enter non-numeric values, but when they delete the value in the box a dialog pops up saying:
"Please enter a positive integer."
Here is the situation:
1. I have a number (say 20) in the box.
2. I delete the number.
3. I get the error dialog.
Could anybody tell me how I can intercept this event and put a default value in there?
Here is what my property pane looks like:
const int DEFAULT_VALUE = 20;
class MyPropertyPane:public CPropertyPane
{
//....
private:
CEdit m_NumericBox;
int m_value;
//....
public:
afx_msg void OnEnChangeNumericBox();
//....
}
void MyPropertyPane::MyPropertyPane()
{
// Set a default value
m_value = DEFAULT_VALUE;
}
//....
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
// this sets the displayed value to 20
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
}
//....
void MyPropertyPane::OnEnChangeNumericBox()
{
// Somebody deleted the value in the box and I got an event
// saying that the value is changed.
// I try to get the value from the box by updating my data
UpdateData(TRUE);
// m_value is still 20 although the value is
// deleted inside the text box.
}
The message you are receiving is coming from the data validation routines, not the data exchange routines. There is probably a call like this in DoDataExchange():
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
DDV_MinMaxInt(pDX, m_value, 1, 20); // if the value in m_value is outside the range 1-20, MFC will pop up an error dialog
}
You can fix this problem by removing the built-in MFC data validation and adding your own:
void MyPropertyPane::DoDataExchange(CDataExchange* pDX)
{
DDX_Control(pDX, IDC_NUMERIC_BOX, m_NumericBox);
DDX_Text(pDX, IDC_NUMERIC_BOX, m_value);
if( m_value < 1 || m_value > 20 )
{
m_value = DefaultValue;
}
}
John Dibling's hint led me to this solution:
void MyPropertyPane::OnEnChangeNumericBox()
{
UpdateData(TRUE);
CString value;
m_NumericBox.GetWindowText(value);
if( value.IsEmpty() )
{
m_value = DEFAULT_VALUE;
UpdateData(FALSE);
}
}
The ONLY validation that I really had to do is to check that the box contains a value, since the actual numeric validation is already handled by the box. The user can't enter a non-numeric value, but they can delete the existing one so that was a situation which was hard to handle in the data exchange function and I had to "hack" the OnChange event.
This one worked for me
void CtimersDlg::OnEnChangeInterval()
{
CString value; //or use char *
CWnd *pWnd = GetDlgItem(IDC_INTERVAL);//IDC_EDITBOX
if(pWnd)
{
pWnd->GetWindowTextW(value);
}
int i = _wtoi(value); //if char * use _atol()
if((!value.IsEmpty())&& (i)) //To check i = 0 or 00 entered or not
UpdateData(TRUE);
}