LinuxQuestions.org
Go Job Hunting at the LQ Job Marketplace
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices

Rate this Entry

Generic QT Signal/Slot --or-- Using Dummy Custom Widget in Designer -- No Plugin Req'd

Posted 12-13-2012 at 02:52 PM by rainbowsally

[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
  • 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>&amp;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
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
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
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

:-)
Posted in Uncategorized
Views 668 Comments 0
« Prev     Main     Next »

  



All times are GMT -5. The time now is 11:49 PM.

Main Menu
Advertisement

Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration