Generic QT Signal/Slot --or-- Using Dummy Custom Widget in Designer -- No Plugin Req'd
Posted 12-13-2012 at 01:52 PM by rainbowsally
Tags c++, computer mad science, genericity, makefiles, qt4
[This is FUN!! It works. What a freaking trip. :-)]
The previous entry on using Old Style callbacks wasn't much of a hit. Ooooooohhhh... it's a hit alright.
But here it is in it's second incarnation cleaned up a bit though it needs a bit more simplification due to the number of elements that are involved and where they need to go. But this is cool.
Today's Features
Now...
What if we want to design an interface for a custom widget but we don't want to go through all the steps to create a plugin for QT Designer?
Can we, for example, create a generic widget and then CHANGE it to another kind of widget, let's say, at runtime?
(Note: It should be done at run time, especially while still in the design phase, because Designer's output files will be overwritten every time a change is made.]
Let's cook up a test creating a user interface with a generic widget that we'll later change to something like a TextEdit widget.
Here's a ui file.
file: src/awidget.ui
purpose: Create a window containing a Widget we can replace with a TextEdit
What we notice about the ui above when we load it with QT Designer is that it has something that almost looks like a TextEdit widget. But it's not. It's just a widget with the propery sheet defined as
to sorta tag it as the object we're interested in.
FYI, the Old Style Callback is generic enough to implement all the editor functions you'd ever want to access in a single signal-slot connection.
Anyway...
Here's the mc2.def (Makefile definitions).
file: mc2.def
purpose: source file
Here are the rest of the files for this demo.
file: src/app-main.cpp
purpose: source file
file: src/awidget.cpp
purpose: source file
file: src/awidget.h
purpose: source file
file: src/dispatch.cpp
purpose: source file
file: src/dispatch.h
purpose: source file
The 'dispatch' functions are not yet easy to use. But this is a proof of concept that should blow a few minds.
PS. Check out th clickable backup shell program in the next blog entry.
The Computer Mad Science Team
:-)
The previous entry on using Old Style callbacks wasn't much of a hit. Ooooooohhhh... it's a hit alright.
But here it is in it's second incarnation cleaned up a bit though it needs a bit more simplification due to the number of elements that are involved and where they need to go. But this is cool.
Today's Features
- Using a custom widget in Designer without making the plugin.
- Using the "Old Style" callbacks to implement genericity for objects.
- Method id's by a name hash algorithm.
Now...
What if we want to design an interface for a custom widget but we don't want to go through all the steps to create a plugin for QT Designer?
Can we, for example, create a generic widget and then CHANGE it to another kind of widget, let's say, at runtime?
(Note: It should be done at run time, especially while still in the design phase, because Designer's output files will be overwritten every time a change is made.]
Let's cook up a test creating a user interface with a generic widget that we'll later change to something like a TextEdit widget.
Here's a ui file.
file: src/awidget.ui
purpose: Create a window containing a Widget we can replace with a TextEdit
Code:
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>aWidget</class> <widget class="QWidget" name="aWidget"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>411</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> <widget class="QWidget" name="Object" native="true"> <property name="geometry"> <rect> <x>10</x> <y>10</y> <width>391</width> <height>201</height> </rect> </property> <property name="styleSheet"> <string notr="true">background-color:white</string> </property> </widget> <widget class="QPushButton" name="pushButton"> <property name="geometry"> <rect> <x>140</x> <y>240</y> <width>102</width> <height>27</height> </rect> </property> <property name="text"> <string>Hide</string> </property> </widget> <widget class="QPushButton" name="pbQuit"> <property name="geometry"> <rect> <x>290</x> <y>240</y> <width>102</width> <height>27</height> </rect> </property> <property name="text"> <string>&Quit</string> </property> </widget> </widget> <resources/> <connections> <connection> <sender>pushButton</sender> <signal>clicked()</signal> <receiver>aWidget</receiver> <slot>toggleHide()</slot> <hints> <hint type="sourcelabel"> <x>170</x> <y>253</y> </hint> <hint type="destinationlabel"> <x>199</x> <y>149</y> </hint> </hints> </connection> <connection> <sender>pbQuit</sender> <signal>clicked()</signal> <receiver>aWidget</receiver> <slot>close()</slot> <hints> <hint type="sourcelabel"> <x>307</x> <y>240</y> </hint> <hint type="destinationlabel"> <x>321</x> <y>155</y> </hint> </hints> </connection> </connections> <slots> <slot>toggleHide()</slot> </slots> </ui>
What we notice about the ui above when we load it with QT Designer is that it has something that almost looks like a TextEdit widget. But it's not. It's just a widget with the propery sheet defined as
Code:
background-color:white
FYI, the Old Style Callback is generic enough to implement all the editor functions you'd ever want to access in a single signal-slot connection.
Anyway...
Here's the mc2.def (Makefile definitions).
file: mc2.def
purpose: source file
Code:
# mc2.def template created with Makefile Creator 'mc2' # change main to *.so for a lib or plugin OUTNAME = widget2editor EXT_ALL = ext_all # Optionally include global or semi-global defs for your makefile here. # include ../makeinclude # typical location for a PROJECT makeinclude # if the current makefile is under a PROJECT # my debug versions of all the QT libs are currently here QT_DBGLIB = -L/opt/qt4/lib # Note: the *DIR, COMPILE and LINK defs can't join lines due to the way # they are imported from the environment but user defined variables will # accept the backslash to join lines to create vertical lists. See the # HDR and OBJ lists in the generated Makefile for example. ## The directories for sources, (temp) objects, and binary output(s) ## (single lines for BINDIR, SRCDIR, OBJDIR) BINDIR = . SRCDIR = src OBJDIR = o # example list of additional flags for a plugin. multi-line = ok. DEFINES = \ -D_REENTRANT \ -DQT_QT3SUPPORT_LIB \ -DQT3_SUPPORT \ -DQT_GUI_LIB \ -DQT_CORE_LIB \ ############### # -DQT_SHARED \ # -DQT_PLUGIN \ # ############## ## What COMPILE should do. ## (single lines for COMPILE, CFLAGS, INCLUDE) COMPILE = g++ -c CFLAGS = $(DEFINES) -Wall -W -g3 # # debuggable # add -fPIC and -D_REENTRANT to the DEFINES list for shared libs and plugins # change -O2 to -g3 for debug versions INCLUDE = -I$(SRCDIR) -I/usr/include -I/usr/include/Qt -I/usr/include/QtGui -I /usr/include/QtCore # add these as needed to the includes above. # -I /usr/include/Qt -I /usr/include/QtHelp -I /usr/include/QtSvg # -I /usr/include/Qt3Support -I /usr/include/QtMultimedia -I /usr/include/QtTest # -I /usr/include/QtCore -I /usr/include/QtNetwork -I /usr/include/QtUiTools # -I /usr/include/QtDBus -I /usr/include/QtOpenGL -I /usr/include/QtWebKit # -I /usr/include/QtDeclarative -I /usr/include/QtScript -I /usr/include/QtXml # -I /usr/include/QtDesigner -I /usr/include/QtScriptTools -I /usr/include/QtXmlPatterns # -I /usr/include/QtGui -I /usr/include/QtSql # If you have lots of them and want to join lines (i.e., '\') list them under # DEFINES similar to the way HDR or OBJ defs look in the Makefile and add $(DEFINES) # to the CFLAGS ## What LINK should do. ## (single lines for LINK, LDFLAGS, LIB) LINK = g++ LDFLAGS = -lQtGui -lQtCore # add as needed. see notes for DEFINES and INCLUDES above if you need to split lines # -lQt3Support -lQtNetwork -lQtCLucene # -lQtOpenGL -lQtCore -lQtScript # -lQtDBus -lQtScriptTools -lQtDeclarative # -lQtSql -lQtDesignerComponents -lQtSvg # -lQtDesigner -lQtTest -lQtGui # -lQtWebKit -lQtHelp -lQtXmlPatterns # -lQtMultimedia -lQtXml # adjust the LIB paths for your system. Try 'locate libQtGui' # to find the right path for your system. LIB = -L/usr/lib ################################################################ ## User Defined Targets clean: @rm -f $(MAIN) @rm -f $(OBJ) @rm -f *~ */*~ */*/*~ */*/*/*~ # example MOC_INCLUDE = -I $(SRCDIR) ui: @uic src/awidget.ui -o src/ui_awidget.h @moc src/awidget.h -o src/moc_awidget.cpp @moc src/dispatch.h -o src/moc_dispatch.cpp @mc2 -update # add new files to the makefile src/ui_done: make ui @echo "--------------------------------------------" @echo "Creating new ui and moc files... Done." @echo "Pls repeat previous operation now." @echo "This file is just a flag for the Makefile" > src/ui_done @echo "--------------------------------------------" @exit 1 ext_all: src/ui_done clean-ui: @rm -f src/ui_*.h src/moc_*.cpp src/qrc_*.cpp @rm -f src/moc_*.o src/qrc_*.o @rm -f src/ui_done # remove the flag semiclean: clean-ui @rm -f $(OBJ) @rm -f *~ */*~ */*/*~ */*/*/*~ force:
Here are the rest of the files for this demo.
file: src/app-main.cpp
purpose: source file
Code:
#include <QApplication> #include <QtGui> // connect() #include <QTextEdit> #include <QDesktopWidget> #include "awidget.h" #include "ui_awidget.h" #include "dispatch.h" // see implementation below main() void dbg(){} void lqCenterWindow(QApplication* app, QWidget* window); extern "C" long main_wndproc(void* hwnd, uint msg, int wparam, long lparam); Dispatch dispatcher; int main(int argc, char *argv[]) { dbg(); // this sets the global qApp instance new QApplication (argc, argv); aWidget w; //////////////////////////////////////////////////////////////////// // replace the old widget with our new one, which we'll model as // a TextEdit widget for this test. QWidget* old_object = w.ui->Object; QTextEdit* new_object = new QTextEdit((QWidget*)&w, "Hey!"); // set up the sizes. new_object->setGeometry( old_object->x(), old_object->y(), old_object->width(), old_object->height()); // The parent linkage is handled by the widget's parent, so we // apparently don't need to reparent anything here. Now we // set the new object where the old one was and delete the // no longer useful old one. w.ui->Object=(QWidget*)new_object; delete old_object; // connect up the old style callback for the dispatcher QObject::connect( &w, SIGNAL(sendMessage(void*,uint,int,long)), &dispatcher, SLOT(receiveMessage(void*,uint,int,long)) ); // and install the handler add_handler(main_wndproc); //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// w.show(); lqCenterWindow(qApp, &w); return qApp->exec(); } void lqCenterWindow(QApplication* app, QWidget* window) { // Unreliable if window isn't shown yet if(!window->isVisible()) return; int dtx,dty,dtw,dth; int winx,winy,winw,winh; int x, y; QDesktopWidget* dt = app->desktop(); // this may not be working. -rs dt->availableGeometry().getRect(&dtx,&dty,&dtw,&dth); window->geometry().getRect(&winx,&winy,&winw,&winh); x = dtx + dtw/2 - winw/2; // x1 should not always be 0 y = dty + dth/2 - winh/2; // y1 "" window->setGeometry(x, y, winw, winh); // get it up and shown in the right position now. app->processEvents(); // so far } // Here's how we use the old style callbacks to relay a message to the // new widget we set in place of the old one in main() extern "C" { uint hash32(const char* name); // a message handler for our app long main_wndproc(void* hwnd, uint msg, int wparam, long lparam) { // hwnd is the caller's object // uint msg must be an agreed upon id for a method call // wparam is an integer or short parameter(s) // lparam is a pointer or a long integer, depending on // the context. // the hash function can be used either to #define the a value // or to generate it as needed. uint LQ_ADD_TEXT = hash32("LQ_ADD_TEXT"); // handle the various cases here. if(msg == LQ_ADD_TEXT) { // hwnd is the caller's object. It is the container holding the // class that now has a TextEdit widget where it once had a Widget. // The name of the widget is the same so we can address it like this. aWidget* classptr = (aWidget*)hwnd; QTextEdit* te = (QTextEdit*)classptr->ui->Object; // Now we're sure to have the correct instance of this widget and // it is set to be the correct type by casting it to a TextEdit. // The string we expect can only be in the lparam. It's the only // field large enough to handle a string (char*), and so some degree // of type safety is inherent in this API. The only good thing about // windows. te->append((char*)lparam); te->update(); // and all the rest of the public TextEdit calls are // likewise accessible. return true; // has been handled } else return default_handler(hwnd, msg, wparam, lparam); } uint hash32(const char* name_8ebp) { // the only way a 0 is returned is if it's a null pointer if(!name_8ebp) return 0; const char* p_edx = name_8ebp; uint prime_ecx = 16777619; uint acc_eax = (uint)-2128831035; while(*p_edx) { acc_eax ^= *p_edx++; acc_eax *= prime_ecx; } // make sure high bit is set, so even 0 // has bits set. if(acc_eax & 0x80000000) return acc_eax; else return ~acc_eax; } } // extern "C"
file: src/awidget.cpp
purpose: source file
Code:
// file: awidget.cpp #include "awidget.h" #include "ui_awidget.h" #include "dispatch.h" #define LQ_ADD_TEXT 0xECD09C73 /* name hash */ aWidget::aWidget(QWidget *parent) : QWidget(parent), ui(new Ui::aWidget) { ui->setupUi(this); } aWidget::~aWidget() { delete ui; } void aWidget::toggleHide() { static bool state; if(state) { emit sendMessage( this, // the caller's object LQ_ADD_TEXT, // method selector 0, // int type (long)"Showing"); // long or pointer types ui->Object->show(); ui->pushButton->setText("Hide"); } else { emit sendMessage( this, // the caller's object LQ_ADD_TEXT, // method selector 0, // int type (long)"Hiding"); // long or pointer types ui->Object->hide(); ui->pushButton->setText("Show"); } state = !state; }
file: src/awidget.h
purpose: source file
Code:
// file: awidget.h #ifndef AWIDGET_H #define AWIDGET_H #include <QWidget> namespace Ui { class aWidget; } class aWidget : public QWidget { Q_OBJECT public: explicit aWidget(QWidget *parent = 0); ~aWidget(); // private: Ui::aWidget *ui; public slots: void toggleHide(); signals: void sendMessage(void* hwnd, uint msg, int wparam, long lparam); }; #endif // AWIDGET_H
file: src/dispatch.cpp
purpose: source file
Code:
#include "dispatch.h" #define UNUSED(x) x=x Dispatch::Dispatch() { } Dispatch::~Dispatch() { } extern "C" { // used to call previous in list long default_handler(void* hwnd, uint msg, int wparam, long lparam); // terminate the checks long root_handler(void* hwnd, uint msg, int wparam, long lparam) { UNUSED(hwnd); UNUSED(msg); UNUSED(wparam); UNUSED(lparam); return true; } static wndproc* handlers[1024]; // not a linked list in this version static int last_handler; static int handler_idx; // add to end of list void add_handler(wndproc* proc) { if(!last_handler) handlers[last_handler++] = root_handler; handlers[last_handler++] = proc; } // remove specific handler void remove_handler(wndproc* proc); // todo long default_handler(void* hwnd, uint msg, int wparam, long lparam) { return handlers[--handler_idx](hwnd, msg, wparam, lparam); } } // extern "C" void Dispatch::receiveMessage(void* hwnd, uint msg, int wparam, long lparam) { handler_idx = last_handler; if(! last_handler) return; if(!default_handler(hwnd, msg, wparam, lparam)) { // not handled... ignore unless debugging } }
file: src/dispatch.h
purpose: source file
Code:
// main.h - template created with new.header #ifndef dispatch_h #define dispatch_h #include <QObject> // callable from C or ASM extern "C" { // wndproc type, returns non-zero if handled typedef long wndproc(void* hwnd, uint msg, int wparam, long lparam); // used to call previous in list long default_handler(void* hwnd, uint msg, int wparam, long lparam); // add to end of list void add_handler(wndproc* proc); // remove specific handler void remove_handler(wndproc* proc); // todo, not implemented } // extern "C" // receiver, win/qt-compatible, but this qt version doesn't // give us control of the message loop. class Dispatch : public QObject { Q_OBJECT public: Dispatch(); ~Dispatch(); public slots: void receiveMessage(void* hwnd, uint msg, int wparam, long lparam); }; #endif // dispatch_h
PS. Check out th clickable backup shell program in the next blog entry.
The Computer Mad Science Team
:-)