Here is my code :
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
// Without this line, GStreamer will create its own new window for the video stream.
gtk_widget_realize(video_window);
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
were pnlView is define as wxPanel* pnlView;
But the consol give me this error : Impossible to convert 'WXWidget' in 'GtkWidget * at the line where I initialize video_window
Can someone has any idea how to fix it ?
I just want to add my gstreamer window in my wxWindow
Thank you
I've never used gstreamer, but I do sometimes use libvlc which is probably very similar. When using libvlc to render into a wxWindow, I need to wait until the wxWindow is fully created before I can set vlc to use it. This is done by adding an event handler for the window creation event.
The process of declaring and binding the event handler looks like this:
class MainWindow : public wxFrame
{
...
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent& event);
}
MainWindow::MainWindow(...)
{
...
#ifdef __WXGTK__
// On GTK+, we have to wait until the window is actually created before we
// can tell VLC to use it for output. So wait for the window create event.
pnlView->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
#elif defined(__WXMSW__)
m_player.setHwnd(pnlView->GetHandle());
#endif
...
}
For libvlc, my window creation event handler looks like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
m_player.setXwindow(
gdk_x11_window_get_xid(gtk_widget_get_window(pnlView->GetHandle()))
);
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Based on the code you posted, I think the body of an event handler that will work for you should look something like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Edit:
Here's a simple example of using GStreamer to draw onto a wxWindow on GTK. This shows how to use the wxEVT_CREATE to get the XID for a window and how to use GStreamer's bus sync handler callback to pass that XID to GStreamer at the correct time.
This is basically a mashup of the 2nd tutorial and the code snippet from the GstVideoOverlay page adjusted for wxWidgets. Since this is based on the 2nd tutorial, it just shows a test pattern. The source variable can be changed to show other videos.
Obviously this assumes GTK is using X11. Some adjustments would be needed if Wayland is used instead, but I don't have a running distro that uses Wayland to test on, so I don't know what changes are needed there.
#include "wx/wx.h"
#ifdef __WXGTK__
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#endif
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
class MainWindow : public wxFrame
{
public:
MainWindow(const wxString& title);
~MainWindow();
private:
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent&);
void OnPlay(wxCommandEvent&);
void OnStop(wxCommandEvent&);
// Helper function
void LoadVideo();
void PlayHelper();
// wx controls
wxWindow* m_renderWindow;
wxButton* m_playButton;
wxButton* m_stopButton;
// GStreamer data
GstElement* m_pipeline;
guintptr m_xid;
};
MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
// Create the UI widgets.
wxPanel* bg = new wxPanel(this,wxID_ANY);
m_renderWindow = new wxWindow(bg,wxID_ANY);
m_playButton = new wxButton(bg,wxID_ANY,"Play");
m_stopButton = new wxButton(bg,wxID_ANY,"Stop");
m_renderWindow->SetBackgroundColour(*wxBLACK);
m_playButton->Enable(true);
m_stopButton->Enable(false);
// Layout the UI.
wxBoxSizer* szr1 = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* szr2 = new wxBoxSizer(wxHORIZONTAL);
szr2->Add(m_playButton, wxSizerFlags(0).Border(wxLEFT|wxRIGHT|wxBOTTOM));
szr2->Add(m_stopButton, wxSizerFlags(0).Border(wxRIGHT|wxBOTTOM));
szr1->Add(m_renderWindow, wxSizerFlags(1).Expand().Border(wxBOTTOM));
szr1->Add(szr2, wxSizerFlags(0));
bg->SetSizer(szr1);
Layout();
// Set up the event handlers.
#ifdef __WXGTK__
m_renderWindow->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
m_playButton->Enable(false);
#endif
m_playButton->Bind(wxEVT_BUTTON, &MainWindow::OnPlay, this);
m_stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStop, this);
// Initialize GStreamer.
m_xid = 0;
m_pipeline = NULL;
gst_init(NULL, NULL);
}
MainWindow::~MainWindow()
{
if ( m_pipeline )
{
gst_element_set_state(m_pipeline, GST_STATE_NULL);
gst_object_unref(m_pipeline);
}
}
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent&)
{
#ifdef __WXGTK__
// This event is no longer needed.
m_renderWindow->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
// Get the XID for this window.
m_xid = GDK_WINDOW_XID(gtk_widget_get_window(m_renderWindow->GetHandle()));
// We can now load and play the video, so enable the play button.
m_playButton->Enable(true);
#endif
}
void MainWindow::OnPlay(wxCommandEvent&)
{
if ( m_pipeline )
{
PlayHelper();
}
else
{
LoadVideo();
}
}
void MainWindow::OnStop(wxCommandEvent&)
{
if ( m_pipeline )
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the paused state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
}
}
void MainWindow::LoadVideo()
{
// Create the elements
GstElement *source = gst_element_factory_make("videotestsrc", "source");
#ifdef __WXGTK__
GstElement *sink = gst_element_factory_make("xvimagesink", "sink");
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), m_xid);
#elif defined __WXMSW__
GstElement *sink = gst_element_factory_make("d3dvideosink", "sink");
WXWidget hwnd = m_renderWindow->GetHandle();
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink),
reinterpret_cast<guintptr>(hwnd));
#endif
//Create the empty pipeline
m_pipeline = gst_pipeline_new ("test-pipeline");
if ( !m_pipeline || !source || !sink )
{
wxLogError("Not all elements could be created.");
return;
}
// Build the pipeline
gst_bin_add_many(GST_BIN(m_pipeline), source, sink, NULL);
if ( gst_element_link(source, sink) != TRUE )
{
wxLogWarning("Elements could not be linked.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
return;
}
// Modify the source's properties
g_object_set(source, "pattern", 0, NULL);
PlayHelper();
}
void MainWindow::PlayHelper()
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the playing state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(false);
m_stopButton->Enable(true);
}
}
class MyApp : public wxApp
{
public:
bool OnInit() override
{
MainWindow* mainWindow = new MainWindow("wxWidgets GStreamer demo");
mainWindow->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On mint it looks like this:
On windows it looks like this:
Related
According to the documentation, the method wxProcess::IsInputAvailable "allows writing simple (and extremely inefficient) polling-based code waiting for a better mechanism in future wxWidgets versions." It also points to an example code dated to "15.01.00", I've checked on GitHub that the usage of this method dates back some 16 years or more.
Well, is this method of pooling still inefficient? Is there any better alternative?
I'm debugging an issue with a process's output not being read using something similar to the exec sample, and either way my application is sensitive to the timeliness of this polling and extracting of output.
I don't think the methods for reading the redirected output or the child process are inefficient. I think the document is stating that polling on the GUI thread as in the sample will be inefficient since it involves repeatedly generating pseudo idle events.
A possible alternative is to use a secondary thread to do the reading. Here is an example of using the lame mp3 encoder to encode a wav file:
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include <wx/filename.h>
#include <wx/filepicker.h>
#include <wx/msgqueue.h>
#include <wx/thread.h>
#include <wx/process.h>
const char* pathToLame = "d:\\temp\\lame.exe";
wxDEFINE_EVENT(wxEVT_THREAD_STDIN, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_THREAD_STDERR, wxThreadEvent);
class LameThread : public wxThread
{
public:
enum ThreadMessage
{
ProcessComplete,
ExitThread,
MessageLast
};
LameThread(wxEvtHandler*, wxProcess*, wxMessageQueue<ThreadMessage>& q);
~LameThread();
private:
ExitCode Entry() wxOVERRIDE;
void DrainInput();
wxMessageQueue<ThreadMessage>& m_queue;
wxEvtHandler* m_handler;
wxProcess* m_process;
char* m_buffer;
size_t m_bufferSize;
};
LameThread::LameThread(wxEvtHandler* h, wxProcess* p,
wxMessageQueue<ThreadMessage>& q)
:wxThread(wxTHREAD_JOINABLE),m_queue(q)
{
m_process = p;
m_handler = h;
m_bufferSize = 1024*1024;
m_buffer = new char[m_bufferSize];
}
LameThread::~LameThread()
{
delete[] m_buffer;
delete m_process;
}
wxThread::ExitCode LameThread::Entry()
{
ExitCode c;
while ( 1 )
{
// Check if termination was requested.
if ( TestDestroy() )
{
wxProcess::Kill(m_process->GetPid());
c = reinterpret_cast<ExitCode>(1);
break;
}
ThreadMessage m = MessageLast;
wxMessageQueueError e = m_queue.ReceiveTimeout(10, m);
// Check if a message was received or we timed out.
if ( e == wxMSGQUEUE_NO_ERROR )
{
if ( m == ProcessComplete )
{
DrainInput();
c = reinterpret_cast<ExitCode>(0);
break;
}
else if ( m == ExitThread )
{
wxProcess::Kill(m_process->GetPid());
c = reinterpret_cast<ExitCode>(1);
break;
}
}
else if ( e == wxMSGQUEUE_TIMEOUT )
{
DrainInput();
}
}
return c;
}
void LameThread::DrainInput()
{
if ( !m_process->IsInputOpened() )
{
return;
}
wxString fromInputStream, fromErrorStream;
wxInputStream* stream;
while ( m_process->IsInputAvailable() )
{
stream = m_process->GetInputStream();
stream->Read(m_buffer, m_bufferSize);
fromInputStream << wxString(m_buffer, stream->LastRead());
}
while ( m_process->IsErrorAvailable() )
{
stream = m_process->GetErrorStream();
stream->Read(m_buffer, m_bufferSize);
fromErrorStream << wxString(m_buffer, stream->LastRead());
}
if ( !fromInputStream.IsEmpty() )
{
wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDIN);
event->SetString(fromInputStream);
m_handler->QueueEvent(event);
}
if ( !fromErrorStream.IsEmpty() )
{
wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDERR);
event->SetString(fromErrorStream);
m_handler->QueueEvent(event);
}
}
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
void OnClose(wxCloseEvent& event);
void OnEncode(wxCommandEvent& event);
void OnProcessComplete(wxProcessEvent& event);
void OnThreadInput(wxThreadEvent&);
wxThread* m_lameThread;
wxMessageQueue<LameThread::ThreadMessage> m_msgQueue;
wxFilePickerCtrl* m_filePicker;
wxTextCtrl* m_textCtrl;
wxButton* m_encodeButton;
};
MyFrame::MyFrame()
:wxFrame(NULL, wxID_ANY, "Encode", wxDefaultPosition, wxSize(600, 400))
{
m_lameThread = NULL;
wxPanel* panel = new wxPanel(this, wxID_ANY);
m_filePicker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString,
"Select a file", "*.wav");
m_textCtrl = new wxTextCtrl(panel, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY);
m_encodeButton = new wxButton( panel, wxID_ANY, "Encode");
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_filePicker, wxSizerFlags().Expand().Border(wxALL) );
sizer->Add(m_textCtrl,
wxSizerFlags(1).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));
sizer->Add(m_encodeButton,
wxSizerFlags().Border(wxLEFT|wxRIGHT|wxBOTTOM).Right());
panel->SetSizer( sizer );
panel->Layout();
m_encodeButton->Bind(wxEVT_BUTTON, &MyFrame::OnEncode, this);
Bind(wxEVT_CLOSE_WINDOW, &MyFrame::OnClose, this);
Bind(wxEVT_END_PROCESS, &MyFrame::OnProcessComplete, this);
Bind(wxEVT_THREAD_STDIN, &MyFrame::OnThreadInput, this);
Bind(wxEVT_THREAD_STDERR, &MyFrame::OnThreadInput, this);
}
void MyFrame::OnClose(wxCloseEvent& event)
{
if ( m_lameThread && m_lameThread->IsRunning() )
{
m_msgQueue.Post(LameThread::ExitThread);
m_lameThread->Wait();
delete m_lameThread;
}
Destroy();
}
void MyFrame::OnEncode(wxCommandEvent& event)
{
// Make sure the input file exists and is a wav file.
wxString file = m_filePicker->GetPath();
if ( !wxFileName::FileExists(file) )
{
m_textCtrl->AppendText("file does not exist.\n");
return;
}
wxFileName fn(file);
if ( fn.GetExt() != "wav" )
{
m_textCtrl->AppendText("File is not a wav file.\n");
return;
}
// Create a process and and encoder thread.
wxProcess* process = new wxProcess(this);
process->Redirect();
m_msgQueue.Clear();
m_lameThread = new LameThread(this, process, m_msgQueue);
m_lameThread->Run();
if ( !m_lameThread->IsRunning() )
{
m_textCtrl->AppendText("Unable to launch encoder thread.\n");
delete m_lameThread;
return;
}
// Execute the encoder command.
wxString cmd = pathToLame;
cmd << " \"" << fn.GetFullPath() << "\" \"";
cmd << fn.GetPathWithSep() << fn.GetName() << ".mp3\"";
wxExecute(cmd, wxEXEC_ASYNC, process);
m_encodeButton->Disable();
}
void MyFrame::OnProcessComplete(wxProcessEvent& event)
{
if ( m_lameThread && m_lameThread->IsRunning() )
{
m_msgQueue.Post(LameThread::ProcessComplete);
m_lameThread->Wait();
delete m_lameThread;
m_lameThread = NULL;
m_encodeButton->Enable();
}
}
void MyFrame::OnThreadInput(wxThreadEvent& event)
{
m_textCtrl->AppendText(event.GetString());
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
::wxInitAllImageHandlers();
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On windows, this looks like this:
The basic idea is to run a tight loop in the thread's Entry method. In each iteration the thread waits for a message from a message queue, and if no message is received the thread tries to check the process for input or error messages.
That's essentially the same polling technique as used in the sample, but since it's in a secondary thread it won't clog the gui thread with idle events. If you need to do any processing of the input, that will also be done on the secondary thread.
If you're having trouble with timings, you can reduce the time spent waiting on the message queue.
Note that launching the secondary procss and receiving the event when it finishes can only be done on the main thread.
Finally I should mention that I'm not 100% sure this is completely thread safe. I wish there was a way to protect the reads and writes with the input and error streams with a mutex or critcal section; but since all the writing to those streams is done under the hood, I can't see any way to do that from user code. I can say I've used this technique on both windows and linux in the past without issue.
Just implemented a SDL_Renderer in my engine
state_t init_rend(window_t *context,flag_t flags) {
rend.renderer = NULL;
rend.renderer = SDL_CreateRenderer(context,-1,flags);
rend.index = -1;
if (rend.renderer != NULL) {
return TRUE;
} else {
return FALSE;
}
}
In my client/test app:
// Init Base2D game
init_env(VIDEO|AUDIO);
// Init Display display
init_disp(640,480,"Display",RESIZABLE|VISIBLE,make_color(255,255,255,255));
// Init Renderer renderer
init_rend(display->window,SOFTWARE);
// Game Loop
state_t status;
while (TRUE) {
update();
status = listen();
if (!status) {
break;
}
/* User Event Handles */
}
And I could handle window resizing successfully with:
void resize_window() {
printf("I was here!\n");
SDL_FreeSurface(display->container);
printf("Now I am here\n");
display->container = SDL_GetWindowSurface(display->window);
SDL_FillRect(
display->container,
NULL,
SDL_MapRGBA(
display->container->format,
get_red(),
get_green(),
get_blue(),
get_alpha()
)
);
}
However, since I have implemented the renderer, whenever I attempt to resize my display it segfaults when trying to SDL_FreeSurface(display->container).
As I have mentioned, the resizing worked fine until I implemented a renderer.
Why is this happening?
Following the link provided by user:keltar,
It seems to me the way to go with SDL2 is to use a renderer for drawing to the window intead of the old SDL1 method of using a surface.
I did just that, removed the surface code and used a renderer only and the code works without problem
Thank You
I am using GTK+ to create a C++ program. For choosing files I used native file chooser as the gtk file chooser has a memory leak.
Here is my question: How can I set a default file name for the native file chooser in GTK+ (while saving) and how can I setup a file filter for it - to be able to open or save only specific file extensions?
My program works on Win32.
Sample code:
static void cb_SaveFileAs(GtkWidget *caller)
{
GtkWindow *parent_window = GTK_WINDOW(caller);
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
gint res;
native = gtk_file_chooser_native_new("Save File",
parent_window,
action,
"_Save",
"_Cancel");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
char *filename;
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
// save the file
save_to_file(filename);
g_free(filename);
}
g_object_unref(native);
}
Here is an example of setting a GtkFileFilter with the *.cpp pattern to only show C++ files. It also sets the default document name to test.cpp.
compiling with : g++ filechooser.cpp `pkg-config --cflags gtk+-3.0` `pkg-config --libs gtk+-3.0`
#include <gtk/gtk.h>
static void dialog_result(GtkWidget *dialog, gint resp, gpointer data)
{
if (resp == GTK_RESPONSE_OK) {
// do nothing
} else {
gtk_widget_destroy(dialog);
}
}
static void open_dialog(GtkWidget *button, gpointer window)
{
GtkWidget *dialog;
GtkFileFilter *filter;
dialog = gtk_file_chooser_dialog_new("Choose a file:", GTK_WINDOW(window),
GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_SAVE,
GTK_RESPONSE_OK, GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL, NULL);
filter = gtk_file_filter_new();
gtk_file_filter_add_pattern(filter, "*.cpp");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "test.cpp");
gtk_widget_show_all(dialog);
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
if (resp == GTK_RESPONSE_OK)
g_print("%s\n", gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
else
g_print("You pressed the cancel\n");
gtk_widget_destroy(dialog);
}
int main()
{
gtk_init(NULL, NULL);
GtkWidget *window, *button;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "delete_event", G_CALLBACK(gtk_main_quit), NULL);
button = gtk_button_new_with_label("Save");
g_signal_connect(button, "clicked", G_CALLBACK(open_dialog), window);
gtk_container_set_border_width(GTK_CONTAINER(window), 100);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show_all(window);
gtk_main();
}
Here is completed code for GTK native file chooser completed with solution from jackw11111:
static void cb_SaveFileAs(GtkWidget *caller) // callback function
{
GtkWindow *parent_window = GTK_WINDOW(caller); // assuming that the caller is GtkWindow
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
gint res;
native = gtk_file_chooser_native_new("Save File",
parent_window,
action,
"_Save",
"_Cancel");
// Filters
filter = gtk_file_filter_new(); // filter 1
gtk_file_filter_set_name(filter, "Project file");
gtk_file_filter_add_pattern(filter, ".proj");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter);
filter = gtk_file_filter_new(); // filter 2
gtk_file_filter_set_name(filter, "All files");
gtk_file_filter_add_pattern(filter, "*");
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(native), filter);
// default file name
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(native), "my_file_name");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
char *filename;
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
// save the file
save_to_file(filename);
g_free(filename);
}
g_object_unref(native);
}
I have a callback function as follows:
void handle(GtkWidget *widget, gpointer data) {...}
Since I have a lot of widgets for this window, I would like to use this callback as the only handler to avoid writing a bunch of small functions. Initially I thought of using an enum that's stored in the UI class which wraps around the window, and then I would test for it as follows:
UIClass::Signal signal = (UIClass::Signal) data;
switch (signal) {
case UIClass::main_button:
// handle
case UIClass::check_box:
...
}
But the compiler refuses the cast in the first line of that snippet.
Is there a standard way to accomplish this? Or was GTK+ designed to have one handler for every widget?
Store a pointer to a widget in class and pass whole object to the handler:
#include <gtk/gtk.h>
#include <iostream>
struct UIClass
{
GtkWidget* widget1, *widget2, *box;
UIClass()
{
widget1 = gtk_button_new_with_label("button1");
widget2 = gtk_button_new_with_label("button2");
box = gtk_hbox_new(true, 10);
gtk_box_pack_start(GTK_BOX(box), widget1, 0 ,0, 1);
gtk_box_pack_start(GTK_BOX(box), widget2, 0 ,0, 1);
}
static void handle(GtkWidget* sender, UIClass* uiClass)
{
if(sender == uiClass->widget1)
std::cout<<"widget1"<<std::endl;
else if(sender == uiClass->widget2)
std::cout<<"widget2"<<std::endl;
else
std::cout<<"something else"<<std::endl;
}
void connect()
{
g_signal_connect(widget1, "clicked", G_CALLBACK(UIClass::handle), this);
g_signal_connect(widget2, "clicked", G_CALLBACK(UIClass::handle), this);
}
};
int main(int argc, char *argv[])
{
gtk_init (&argc, &argv);
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
UIClass ui;
ui.connect();
gtk_container_add(GTK_CONTAINER(window), ui.box);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
The method I traditionally use of drawing to an OpenGL canvas with wxWidgets is to trigger a 30 hertz timed refresh() from the current OpenGL canvas, which then generates a "wxEVT_PAINT", which I can propogate out to the rest of the frame. I also bind to the wxEVT_PAINT and call refresh() to catch any frame resizes.
In my programs WITHOUT using wxWidgets AUI, this method has worked flawlessly.
If I try to use AUI however, every time I try binding to wxEVT_PAINT, my paint events never trigger. Sometimes binding to the paint event will even stop other events like the timer from triggering.
Is there some special way that AUI treats events, or the wxEVT_PAINT that I'm missing here? What is the best method to draw to a OpenGL window inside of a AUI managed frame? Can anyone provide hints or examples, as documentation seems to be non-existent on this topic concerning AUI.
Edit: For clarity, and I've added my source code below for anyone who would like to help track down the problem. I've removed the OpenGL portions, as I'm really just trying to get the wxEVT_PAINT to trigger my bound handler in the frame when I resize the window.
GeneratedFrame.h
#ifndef __GENERATEDFRAME_H__
#define __GENERATEDFRAME_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/panel.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/menu.h>
#include <wx/frame.h>
#include <wx/aui/aui.h>
class MainFrame : public wxFrame
{
private:
protected:
wxPanel* m_panelMainView;
wxMenuBar* m_menubar2;
public:
MainFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("SimpleAui"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 646,516 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
wxAuiManager m_mgr;
~MainFrame();
};
#endif //__GENERATEDFRAME_H__
GeneratedFrame.cpp
#include "GeneratedFrame.h"
MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
m_mgr.SetManagedWindow(this);
m_mgr.SetFlags(wxAUI_MGR_LIVE_RESIZE);
m_panelMainView = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelMainView->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVECAPTION ) );
m_panelMainView->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_INFOBK ) );
m_mgr.AddPane( m_panelMainView, wxAuiPaneInfo() .Name( wxT("MainView") ).Center() .Caption( wxT("Main View") ).MinimizeButton( true ).PinButton( true ).Dock().Resizable().FloatingSize( wxDefaultSize ).Floatable( false ) );
m_menubar2 = new wxMenuBar( 0 );
this->SetMenuBar( m_menubar2 );
m_mgr.Update();
this->Centre( wxBOTH );
}
MainFrame::~MainFrame()
{
m_mgr.UnInit();
}
AppFrame.h
#ifndef __AppFrame_h__
#define __AppFrame_h__
#include "GeneratedFrame.h"
#include "SimpleAuiApp.h"
class AppFrame : public MainFrame
{
public:
// Constructor/Descructor
AppFrame( wxWindow* parent, ApplicationData* pAppData );
~AppFrame( );
// display update (called from main App on an update event)
void UpdateDisplay( );
private:
// pointer to the main data structure
ApplicationData* m_pAppData;
void OnPaint( wxPaintEvent& event );
};
#endif //__AppFrame_h__
AppFrame.cpp
#include "AppFrame.h"
#include "SimpleAuiApp.h"
#include <iostream>
// Constructor for the frame
AppFrame::AppFrame( wxWindow* parent, ApplicationData* pAppData )
: MainFrame( parent )
{
// Pull in the app data pointer
m_pAppData = pAppData;
// Set the size of the inner drawing area of the frame
SetClientSize( 500, 500 );
// Show the frame
Show();
// Layout the frame
Layout();
// Bind to the wxEVT_PAINT event
Bind( wxEVT_PAINT, &AppFrame::OnPaint, this );
}
// Destructor for the frame
AppFrame::~AppFrame( )
{
// stop the update timer for the application, otherwise a timer update
// event can be generated while data is being deleted
if( m_pAppData->m_pTimer )
{
m_pAppData->m_pTimer->Stop( );
}
}
void AppFrame::OnPaint( wxPaintEvent& event )
{
std::cout << "Running AppFrame::OnPaint\n";
UpdateDisplay( );
}
// perform frame update for the display
void AppFrame::UpdateDisplay( )
{
std::cout << "Running AppFrame::UpdateDisplay\n";
}
SimpleAuiApp.h
#ifndef __SimpleAuiApp_h__
#define __SimpleAuiApp_h__
#include <wx/wx.h>
#define DEFAULT_UPDATE_RATE (10) // in Hz (set to 0 for OnIdle)
// Forward declarations
class AppFrame;
struct ApplicationData
{
// constructor
ApplicationData( )
{
m_pFrame = NULL;
m_pTimer = NULL;
m_nDisplayUpdateRate = DEFAULT_UPDATE_RATE;
}
// timer object for frame based updates
wxTimer* m_pTimer;
// rate of display update (in HZ) (0=update on idle)
int m_nDisplayUpdateRate;
// pointer to the main frame instance
AppFrame* m_pFrame;
};
// Main application class
// (derived from the wxWidget App class)
class SimpleAuiApp : public wxApp
{
public:
SimpleAuiApp( );
virtual ~SimpleAuiApp( );
// the main application data structure
ApplicationData m_AppData;
private:
// called by wxApp when starting up, program setup should be done here
bool OnInit( );
// called by wxApp when shutting down, program cleanup should be done here
int OnExit( );
// When running with "fixed" framerate, called for each timer event (frame)
void OnTimer( wxTimerEvent& event );
};
DECLARE_APP( SimpleAuiApp )
#endif //__SimpleAuiApp_h__
SimpleAuiApp.cpp
#include <wx/wx.h>
#include "SimpleAuiApp.h"
#include "AppFrame.h"
#include <iostream>
IMPLEMENT_APP( SimpleAuiApp )
SimpleAuiApp::SimpleAuiApp( )
{
}
SimpleAuiApp::~SimpleAuiApp( )
{
}
bool SimpleAuiApp::OnInit( )
{
// Open a console window for errors and standard output
AllocConsole( );
freopen( "CONOUT$", "wb", stdout );
freopen( "CONOUT$", "wb", stderr );
std::cout << "Initialization started...\n";
// Create the main application frame
m_AppData.m_pFrame = new AppFrame( (wxWindow*) NULL, &m_AppData );
// Bring the frame to the front
SetTopWindow( m_AppData.m_pFrame );
// See if a fixed frame rate is specified
if ( m_AppData.m_nDisplayUpdateRate > 0 )
{
// Start a timer to update the display at a fixed frame rate.
// Note that rate is increased by 10% to make up for wxWidget's inaccurate timers.
m_AppData.m_pTimer = new wxTimer( this );
float fMilliSeconds = 1000.0 / ( m_AppData.m_nDisplayUpdateRate * 1.1f );
m_AppData.m_pTimer->Start( fMilliSeconds );
Bind( wxEVT_TIMER, &SimpleAuiApp::OnTimer, this );
}
else
{
// capture the "on idle" event when not running at a fixed frame rate
Bind( wxEVT_IDLE, &SimpleAuiApp::OnIdle, this );
}
std::cout << "Initialization completed...\n";
return true;
}
// -------------------------------------------------------------
// Framerate - functions bound to framerate related events.
// Either OnTimer() or OnIdle() should be called here for each
// frame, but never both. They are two different refresh
// methods.
// -------------------------------------------------------------
// Called by widget app code on timer event
void SimpleAuiApp::OnTimer( wxTimerEvent& event )
{
std::cout << "Called SimpleAuiApp::OnTimer \n";
// update the frame's display
m_AppData.m_pFrame->UpdateDisplay( );
}
// -------------------------------------------------------------
// OnExit - Called by widget app code on shutdown
// -------------------------------------------------------------
int SimpleAuiApp::OnExit( )
{
// stop (and delete) the update timer if needed
if ( m_AppData.m_pTimer )
{
m_AppData.m_pTimer->Stop( );
delete m_AppData.m_pTimer;
m_AppData.m_pTimer = NULL;
}
// close the console window if needed
FreeConsole( );
// exit successful
return 0;
}
The problem ended up being that the AUI manager which you attach to the main Frame DOES consume the wxEVT_PAINT event when it propagates, and it never reaches the Frame's class. The Frame's children however, do receive the events.
Instead of just using Bind() from the Frame class, I called m_panelgl->Bind where the m_panelgl was my child container which held the OpenGL canvas.