how to split log messages into file and screen in wxwidgets - c++

When I read the WxWidgets documentation, I get the impression that the developers wrote it just for themselves, just to remember what they did 20 years ago.
Regardless, I figured out how to send log messages to a file:
wxLog::SetActiveTarget(new wxLogStderr(fopen(logPath + "/wxApp.log", "w + ")));
and also I figured out how to change the format of the log messages:
wxLog::GetActiveTarget()->SetFormatter(new MyLogger);
But I didn't understand anything else.
So I want to ask my question here.
I want to make a log for my application.
Moreover, I want:
all log messages to be written to a file
at the same time some of these messages are displayed on the screen using wxTextCtrl.
So I want to filter the log messages that are displayed on the screen, depending on the logging level: for example, I want to display in wxTextCtrl only log messages with "wxLOG_Info" and "wxLOG_Error" levels.
How can this be done in Windows and Linux in C++? It's best to show a code example.

I may be missing something but this seems very simple?
For example, this could be the simplest possible log target which logs some messages into a wxTextCtrl and all of them into a wxFFile.
#include <wx/wx.h>
#include <wx/ffile.h>
class MyLogTarget : public wxLog
{
public:
// textCtrl must have longer lifetime than MyLogTarget
MyLogTarget(wxTextCtrl* textCtrl, const wxString& fileName)
: m_textCtrl(textCtrl), m_file(fileName, "a")
{}
protected:
void DoLogTextAtLevel(wxLogLevel level, const wxString& msg) override
{
// preserve debug logging
if ( level == wxLOG_Debug || level == wxLOG_Trace )
wxLog::DoLogTextAtLevel(level, msg);
if ( level == wxLOG_Info || level == wxLOG_Error )
m_textCtrl->AppendText(msg + "\n");
if ( m_file.IsOpened() )
m_file.Write(msg + "\n");
}
private:
wxTextCtrl* m_textCtrl;
wxFFile m_file;
};
class MyFrame : public wxFrame
{
public:
MyFrame(wxWindow* parent = nullptr) : wxFrame(parent, wxID_ANY, "Test")
{
wxTextCtrl* logCtrl = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
wxLog::SetActiveTarget(new MyLogTarget(logCtrl, "log.txt"));
wxLogDebug("Debug test");
wxLogMessage("Message test");
wxLogInfo("Info test");
wxLogError("Error test");
}
~MyFrame()
{
delete wxLog::SetActiveTarget(nullptr);
}
};
class MyApp : public wxApp
{
public:
bool OnInit() override
{
(new MyFrame())->Show();
return true;
}
}; wxIMPLEMENT_APP(MyApp);
Please notice that this would have to be extended to be usable in a real application, for example, handle the file encoding / flushing / cleaning / error handling (without getting into the endless logging loop), use the full path for the file (e.g., obtained with wxStandardPaths), use a log chain to preserve (some of?) the default wxWidgets logging...
If you want to use separate log targets for logging into a text control and a file, it is still very simple except that you have to chain the log targets, as explained in the docs.

Related

How to dynamically retranslate all widgets in application through a menu?

I am working on a Qt project consisting in a QMainWindow and multiple Qt and non-Qt classes. Many of them use QStrings with tr() that are translated with Qt Linguist. The language change (QTranslator load & install/QTranslator load & remove) is triggered by QActions in the app's menu.
I have read the official Qt documentation concerning dynamic translation, and it basically suggests the following overload:
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
titleLabel->setText(tr("Document Title"));
... // all my tr() QStrings here
okPushButton->setText(tr("&OK"));
} else
QWidget::changeEvent(event);
}
The problem I am facing is that the QStrings to translate are many (58 in QMainWindow alone), and several are filled at runtime as well, through user interaction; e. g. myFunction(a,b) below is called through a QPushButton:
void MainWindow::myFunction(MyClassA a, MyClassB b)
{
...
if(b.myCondition() == 0)
{
...
// below is the problem
myLabel->setText(myLabel->text() + QString("\n" + a->getName() + tr(" gagne ") + exp + tr(" points d'expérience")));
}
else
{
myLabel->setText(QString(tr("something else")));
}
...
}
So I hardly see how I can include this type of QString in the changeEvent() method above. What about the classes outside of MainWindow, which also have QStrings to translate but are not QWidget (so no changeEvent overload possible) ?
I have read that there's another way to use this method, with a UI form:
void MainWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange)
{
ui.retranslateUi(this);
}
...
}
But this involves that I am using a UI form in my project, which I am not doing (all widgets are created in code).
I tried to export my MainWindow in a UI form, but when I try to include the generated header into the project, I get the following error:
ui_fenetreprincipale.h:32: error: qmainwindowlayout.h: No such file or directory
Thank you by advance for any suggestion to select the best way to translate my application.
Organize your code so that all the setting of translatable strings is done in one method in each class.
eg give every class that has translatable strings a setTrs() method that actually sets the strings.
class A
{
void setTrs()
{
okPushButton->setText(tr("&OK"));
}
}
//--------------
class B
{
int _trCond;
void myFunction(MyClassA a, MyClassB b)
{
_trCond = b.myCondition();
setTrs();
}
void setTrs()
{
if(_trCond == 0)
myLabel->setText(myLabel->text() + QString("\n" + a->getName() + tr(" gagne ") + exp + tr(" points d'expérience")));
else
myLabel->setText(QString(tr("something else")));
}
Then whenever the language for the application changes (eg connect to a menu entry selection, or MainWindow::event() or however the required language can change), you have to manually call the setTrs method of each of these objects
eg
void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange)
{
setTrs();
objA.setTrs();
objB.setTrs();
}
}
Even more elegant would be to store the objects in a QList and just iterate through it calling the setTrs method on each element in turn
I had the same problem, menus not translating, fixed completely by creating and installing the QTranslator...
QScopedPointer<QApplication> app(new QApplication(argc, argv));
QTranslator myappTranslator;
myappTranslator.load(QString("Languages/de"))
app->installTranslator(&myappTranslator);
...before creating and showing the main window...
MainWindow *mainWin;
mainWin = new MainWindow(&splash);
mainWin->show();

QWebEngineView Open In External Browser

I'm in the process of moving my code from QtWebKit to QtWebEngine. In general, the transition has been fairly smooth, however, I'm stuck on one particular issue. I use a QWebEngineView to display a Google Maps page. Some of the markers placed have have infowindows that pop up "Click Here for More Information" which opens the link in an external browser.
Using QtWebKit, this was fairly easy through the setLinkDelegation policy. However, it seems a little more complex here. I've tried to follow the example but somehow I need to redefine QWebEnginePage within QWebEngineView. Below is what I've come up with so far. Any idea how I can actually connect this all up?
Thanks
#ifndef MYQWEBENGINEVIEW_H
#define MYQWEBENGINEVIEW_H
#include <QWebEngineView>
#include <QDesktopServices>
class MyQWebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
MyQWebEnginePage(QObject* parent = 0) : QWebEnginePage(parent){}
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
qDebug() << "acceptNavigationRequest("<<url << "," << type << "," << isMainFrame<<")";
if (type == QWebEnginePage::NavigationTypeLinkClicked)
{
QDesktopServices::openUrl(url);
return false;
}
return true;
}
};
class MyQWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
MyQWebEngineView(QWidget* parent = 0);
MyQWebEnginePage* page() const;
};
#endif // MYQWEBENGINEVIEW_H
You don't need the second part. Try this:
QWebEngineView *view = new QWebEngineView();
MyQWebEnginePage *page = new MyQWebEnginePage();
view->setPage(page);

QFileDialog: How to select multiple files with touch-screen input?

In our application that uses Qt 4 and supports touch input, we use the QFileDialog with the options QFileDialog::DontUseNativeDialog and QFileDialog::ExistingFiles.
The first is needed because we set our own stylesheet and that does not work with the native dialog. The second is for needed for selecting multiple files, which is what we want to do.
The problem ist that one can not select multiple files with touch input in the QFileDialog, because we have no "shift" or "ctrl"-key available. In Windows the problem is solved by adding checkboxes to the items. QFileDialog has no checkboxes.
I tried to manipulate the QFileDialog to make it displays check boxes for the items, but I failed.
I tried to exchanged the QFileSystemModel that is used by the underlying QTreeView and QListView, but this breaks the signal-slot connections between the model and the dialog. I could not find a way to restore them because they are burried deep in the private intestants of the dialog.
At this moment the only solution I can imagine is writing a whole new dialog, but I would like to avoid the effort.
So is there a way to add checkboxes to the QFileDialog model views ?
Do you have another idea how selecting multiple files could be made possible?
Is the problem fixed in Qt 5? We want to update anyway.
Thank you for your Help.
As I failed to add checkboxes to the item views, I implemented a "hacky" work-around. It adds an extra checkable button to the dialog that acts as a "ctrl"-key. When the button is checked, multiple files can be selected. The solution is a little bit ugly, because it relies on knowing the internals of the dialog, but it does the job.
So here is the code for the header ...
// file touchfiledialog.h
/*
Event filter that is used to add a shift modifier to left mouse button events.
This is used as a helper class for the TouchFileDialog
*/
class EventFilterCtrlModifier : public QObject
{
Q_OBJECT;
bool addCtrlModifier;
public:
EventFilterCtrlModifier( QObject* parent);
void setAddCtrlModifier(bool b);
protected:
virtual bool eventFilter( QObject* watched, QEvent* e);
};
/*
TouchDialog adds the possibility to select multiple files with touch input to the QFileDialog.
This is done by adding an extra button which can be used as control key replacement.
*/
class QTOOLS_API TouchFileDialog : public QFileDialog
{
Q_OBJECT
EventFilterCtrlModifier* listViewEventFilter;
EventFilterCtrlModifier* treeViewEventFilter;
bool initialized;
public:
TouchFileDialog( QWidget* parent);
protected:
virtual void showEvent( QShowEvent* e);
private slots:
void activateCtrlModifier(bool b);
private:
void initObjectsForMultipleFileSelection();
};
with the implementation ...
// file touchfiledialog.cpp
#include "touchfiledialog.h"
EventFilterCtrlModifier::EventFilterCtrlModifier(QObject* parent)
: QObject(parent)
, addCtrlModifier(false)
{
}
void EventFilterCtrlModifier::setAddCtrlModifier(bool b)
{
addCtrlModifier = b;
}
bool EventFilterCtrlModifier::eventFilter(QObject* watched, QEvent* e)
{
QEvent::Type type = e->type();
if( type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease)
{
if( addCtrlModifier)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(e);
// Create and post a new event with ctrl modifier if the event does not already have one.
if( !mouseEvent->modifiers().testFlag(Qt::ControlModifier))
{
QMouseEvent* newEventWithModifier = new QMouseEvent(
type,
mouseEvent->pos(),
mouseEvent->globalPos(),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers() | Qt::ControlModifier
);
QCoreApplication::postEvent(watched, newEventWithModifier);
return true; // absorb the original event
}
}
}
return false;
}
//#######################################################################################
TouchFileDialog::TouchFileDialog(QWidget* parent)
: QFileDialog(parent)
, listViewEventFilter(NULL)
, treeViewEventFilter(NULL)
, initialized(false)
{
}
void TouchFileDialog::showEvent(QShowEvent* e)
{
// install objects that are needed for multiple file selection if needed
if( !initialized)
{
if( fileMode() == QFileDialog::ExistingFiles)
{
initObjectsForMultipleFileSelection();
}
initialized = true;
}
QFileDialog::showEvent(e);
}
void TouchFileDialog::initObjectsForMultipleFileSelection()
{
// install event filter to item views that are used to add ctrl modifiers to mouse events
listViewEventFilter = new EventFilterCtrlModifier(this);
QListView* listView = findChild<QListView*>();
listView->viewport()->installEventFilter(listViewEventFilter);
treeViewEventFilter = new EventFilterCtrlModifier(this);
QTreeView* treeView = findChild<QTreeView*>();
treeView->viewport()->installEventFilter(treeViewEventFilter);
QGridLayout* dialogLayout = static_cast<QGridLayout*>(layout()); // Ugly because it makes assumptions about the internals of the QFileDialog
QPushButton* pushButtonSelectMultiple = new QPushButton(this);
pushButtonSelectMultiple->setText(tr("Select multiple"));
pushButtonSelectMultiple->setCheckable(true);
connect( pushButtonSelectMultiple, SIGNAL(toggled(bool)), this, SLOT(activateCtrlModifier(bool)));
dialogLayout->addWidget(pushButtonSelectMultiple, 2, 0);
}
void ZFFileDialog::activateCtrlModifier(bool b)
{
listViewEventFilter->setAddCtrlModifier(b);
treeViewEventFilter->setAddCtrlModifier(b);
}
The TouchFileDialog installs an event filter to the item views that will add a ControlModifier to the mouse events of the views when the corresponging button in the dialog is checked.
Feel free to post other solutions, because this is somewhat improvised.

Qt How to Prevent dropping item FROM application TO Windows File Explorer

I'm looking for a clean and cross-platform way to Prevent dropping an item FROM a Qt application TO Windows File Explorer (or other OS equiv.)
The following diagram shows the desired behavior:
I haven't had luck finding examples online or hacking a work-around together, but it seems like it would be a common-enough use-case that there would be a well designed and implemented solution floating around.
What I've tried and do not have working:
Detecting the Drag and Killing It:
detecting the QDragEnterEvent, QDragMoveEvent, QDragLeaveEvent
comparing the answerRect() or pos() of the event to the Geometry of
the Window or Widget to detect if the drag has left the application
This is pretty hacky (and not working at them moment) and I'm hoping you can point me towards a more elegant solution.
(UPDATE - tried changing mimeType, but Windows File Explorer still accepts the drop)
Changing the MIME Type to a custom type:
Pre: the "Widget w/ Drag & Drop" from the diagram above is a QTreeView with a QFileSystemModel model
Sub-classing the QFileSystemModel and overriding the mimeTypes() function like the code below
From the qDebug() output, it looks like the mimeType is correctly being set, but Windows File Explorer still accepts the drop :/
QStringList MyFileSystemModel::mimeTypes() const
{
QStringList customMimeTypes;
customMimeTypes << QString("UnicornsAndRainbows/uri-list");
qDebug() << "customMimeTypes: " << customMimeTypes;
return customMimeTypes;
}
Please let me know when you have a chance.
Thanks! :)
Dmitry Sazonov gave the correct answer. I will explain how I implemented it below. Dmitry, if you want cred, post it as an answer and not a comment so I can accept it as the answer.
What I did wrong on my question update based on Dmitry's suggestion was to override the QFileSystemModel::mimeTypes() when, in fact, I had to modify the QTreeView::mouseMoveEvent() and QTreeView::dropEvent().
//---------------------------------------------------------
void MyTreeView::mouseMoveEvent( QMouseEvent *event )
{
if( !(event->buttons() & Qt::LeftButton) )
{
return; // we only care about left mouse drags at the moment
}
if( (event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance() )
{
return; // a buffer when calculating waht qualifies as a "drag event"
}
QDrag *drag = new QDrag( this );
QMimeData *mimeData = new QMimeData();
QByteArray data;
const QStringList selectedPaths = this->getSelectedPaths(); // custom helper method
foreach( QString path, selectedPaths )
{
data.append( path ).append( ";" ); // using ';' as path deliminator
}
data.chop( 1 );
//--- this sets the custom MIME Type filter
mimeData->setData( CUSTOM_MIMETYPE_STRING, data );
drag->setMimeData( mimeData );
Qt::DropAction dropAction = drag->exec( Qt::CopyAction );
}
//---------------------------------------------------------
void MyTreeView::dropEvent( QDropEvent *event )
{
// ...
QList<QByteArray> paths;
//--- this filters based on our custom MIME Type
paths = event->mimeData()->data( CUSTOM_MIMETYPE_STRING ).split(';');
foreach( QByteArray path, paths )
{
// do something with the file paths
}
}

Multithreading in wxWidgets GUI apps?

I'm having problem (basically, I'm confused.) while trying to create a worker thread for my wxWidgets GUI application that WILL MODIFY one of the GUI property itself. (In this case, wxTextCtrl::AppendText).
So far, I have 2 source files and 2 header files for the wx program itself (excluding my own libs, MySQL lib, etc), say MainDlg.cpp which contains a derived class of wxFrame called 'MainDlg' and MainForm.cpp which contains a derived class of wxApp called 'MainForm'.
MainForm.cpp
#include "MainHeader.h" // contains multiple header files
IMPLEMENT_APP(MainForm)
bool MainForm::OnInit()
{
MainDlg *Server = new MainDlg(wxT("App Server 1.0"), wxDEFAULT_FRAME_STYLE - wxRESIZE_BORDER - wxMAXIMIZE_BOX);
Editor->Show();
return true;
}
MainDlg.cpp:
#include "MainHeader.h"
BEGIN_EVENT_TABLE(MainDlg, wxFrame)
EVT_BUTTON(6, MainDlg::StartServer)
EVT_BUTTON(7, MainDlg::StopServer)
END_EVENT_TABLE()
CNETServerConnection *cnServCon;
std::string ServerIP, DBHost, DBUser, DBName, DBPass;
int UserCapacity, DBPort, ServerPort;
MYSQL *sqlhnd;
MainDlg::MainDlg(const wxString &title, long style) : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(301, 230), style)
{
cnServCon = new CNETServerConnection(100);
this->InitializeComponent();
}
void MainDlg::InitializeComponent()
{
this->SetTitle(wxT("App Server 1.0"));
this->SetSize(396, 260);
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
this->Centre();
statBox = new wxTextCtrl(this, 4, wxT("Welcome to AppServer 1.0\n\n"), wxPoint(10, 10), wxSize(371, 141), wxTE_MULTILINE | wxTE_READONLY);
//.................................
}
void MainDlg::StartServer(wxCommandEvent &event)
{
this->startBtn->Enable(false);
this->AppendStatus(wxT("\nLoading server configuration... "));
//.................................
this->AppendStatus(wxT("OK\n\nServer ready!\n\n"));
// When the server is ready, I need to run a thread
// that will update the statBox (through AppendStatus func or wxTextCtrl::AppendText directly)
// regularly without interrupting the GUI itself.
// Because the thread will contain a while-loop
// to make the program keep receiving message from clients.
this->startBtn->Hide();
this->stopBtn->Show();
this->cmdBtn->Enable();
this->cmdBox->Enable();
}
void MainDlg::StopServer(wxCommandEvent &event)
{
//...................................
}
void MainDlg::AppendStatus(const wxString &message)
{
statBox->AppendText(message);
}
// Well, here is the function I'd like to run in a new thread
void MainDlg::ListenForMessages()
{
int MsgSender = 0;
while(1)
{
if(!cnServCon->GetNewMessage())
continue;
if(MsgSender = cnServCon->GetJoiningUser())
this->AppendStatus(wxT("Someone connected to the server."));
}
}
I also found an usage example of wxThread from Simple example of threading in C++:
class MessageThread : public wxThread
{
private:
MessageThread(const MessageThread &copy);
public:
MessageThread() : wxThread(wxTHREAD_JOINABLE)
{
}
void *Entry(void)
{
// My works goes here
return;
}
};
wxThread *CreateThread()
{
wxThread *_hThread = new MessageThread();
_hThread->Create();
_hThread->Run();
return _hThread;
}
But I don't know how to associate it with my program and make it able to modify my GUI property (statBox).
Any kind of help would be appreciated! :)
Thanks.
The easiest is to create an event type id, and use a wxCommandEvent using the type id, set its string member ("evt.SetText"), and send the event to one of your windows (using AddPendingEvent). In the handler of that event, you can then call AppendText on your control using the text you sent (evt.GetText), because you are in the GUI thread by then.
// in header
DECLARE_EVENT_TYPE(wxEVT_MY_EVENT, -1)
// in cpp file
DEFINE_EVENT_TYPE(wxEVT_MY_EVENT)
// the event macro used in the event table. id is the window id you set when creating
// the `wxCommandEvent`. Either use -1 or the id of some control, for example.
EVT_COMMAND(window-id, event-id, handler-function)
Here is an overview how it works: Custom Events.