Call Method from QT slot in static method - c++

I have a static method which shows a list (Items) and return an array of selected object in Items.
I would like to add a checkbox to reload the list along some parameters. We are using QT 3.3.
//// Static
int CMSUI_InputDialog::FittingList(QWidget* parent,
const CString& Title,
const CStringArray& Items,
bool IsMultiSelect,
int DefaultItem,
bool OkIsDefault,
CArray<int, int>& Selecteds)
{
int ret = 0;
Selecteds.RemoveAll();
/// Create grid
QDialog dialog(parent, 0, true);
dialog.setCaption(QString(Title));
QGridLayout* pLayoutGrid = Init(&dialog, OkIsDefault);
//Create checkBox
QCheckBox* pCheckBox = new QCheckBox(&dialog, "m_pApply_Filter");
pCheckBox->setText("Norm Filter");
pLayoutGrid->addWidget(pCheckBox, 0, 0);
QObject::connect(pCheckBox, SIGNAL(stateChanged(int)), &dialog, SLOT(checkboxClicked()));
/// Create ListBox
QListBox* pList = new QListBox(&dialog);
pList->setMinimumSize(QSize(370, 90));
pList->setSelectionMode(QListBox::Extended);
// Load the list
int Count = Items.GetSize();
for (int i = 0; i < Count; i++)
{
QString QS(Items[i]);
pList->insertItem(QS);
}
if (DefaultItem >= 0 && DefaultItem < Count)
pList->setSelected(DefaultItem, true);
pLayoutGrid->addWidget(pList, 1, 0);
// Connect double clic on QDialog accept
QObject::connect(pList, SIGNAL(doubleClicked(QListBoxItem*)), &dialog, SLOT(accept()));
if (dialog.exec() == QDialog::Accepted)
{
for (int i = 0; i < Count; i++)
{
if (pList->isSelected(i))
Selecteds.Add(i);
}
ret = 1;
}
return ret;
}
void CMSUI_InputDialog::checkboxClicked()
{
//To do
}
checkboxClicked() is declared in CMSUI_InputDialog.h as slot
class CMSUI_InputDialog
{
protected :
static QGridLayout* Init(QWidget* pParent, bool OkIsDefault);
public slots:
void checkboxClicked();
public:
/// CheckBox + ListBox for fittings
static int FittingList(QWidget* parent,
const CString& Title,
const CStringArray& Items,
bool IsMultiSelect,
int DefaultItem,
bool OkIsDefault,
CArray<int, int>& Selecteds);
};
I tried many things but I'm stupid with QT and didn't got any success to catch the action on the checkbox

There's little Qt-specific here as far as the primary problem goes: it's all understandable in plain C++, no need for Qt knowledge.
You're not checking the result returned by the connect method: it returns false when it fails. The connect fails in your case, so there's no point in going any further than that. The slot will not be called. The reason for that: dialog is just a QDialog, not CMSUI_InputDialog. You can't add any slots to an existing Qt class.
Slots must be methods in QObjects. The class where you added the "slot" is not derived from QObject. You'd need to create such a class (to replace the QDialog that you're using).
slots is a macro that is empty (expands to nothing) and thus has no effect on the compilation. It has a purpose, though: the moc (meta object compiler) that processes the header file will notice that macro and process the subsequent methods as slots. It won't do that unless you also have the Q_OBJECT macro within the class.
First, you'd want to factor out the options for that dialog into a FittingOptions struct, to make the code manageable.
struct FittingOptions {
const CStringArray& items,
bool isMultiSelect,
int defaultItem,
bool okIsDefault,
};
The dialog should become a new class, where your slot would go:
class FittingList : public QDialog {
Q_OBJECT
FittingOptions opt;
CArray<int, int>* selecteds;
public:
FittingList(QWidget *parent, const FittingOptions &options) :
QDialog(parent), opt(options), selecteds(0)
{
// TODO: initialization code that creates widgets etc.
}
void setSelecteds(CArray<int, int> &selecteds)
{
this->selecteds = &selecteds;
}
// ...
public slots:
void checkboxChanged() { /* TODO */ }
};
And then, put the initialization code from the FittingList method into the constructor. Change that method into:
int CMSUI_InputDialog::FittingList(QWidget* parent,
const CString& title,
const CStringArray& items,
bool isMultiSelect,
int defaultItem,
bool okIsDefault,
CArray<int, int>& selecteds)
{
const FittingOptions options = {
items, isMultiSelect, defaultItem, okIsDefault
};
::FittingList dialog(parent, options);
dialog.setCaption(QString(title));
dialog.setSelecteds(selecteds);
if (dialog.exec() != QDialog::Accepted) return 0;
return 1;
}
Look at other examples within your project to see how they went about such problems. This code is quite stale by today's standards, but it's a maintenance job as far as I understand, so you got to do more of the same - since I imagine you're not upgrading the Qt version.
Note: this is obviously something that concerns the original author(s) of the code, not you. You haven't written this stuff. Even in Qt 3's times this code would have been considered crusty - the seemingly pervasive use of globals/singletons is cringe-worthy. I always wonder why people who work on such presumably large scale projects won't look into the code they paid for and is available to them: Qt 3 includes full source code to Qt Designer, and that's where one might have looked for inspiration/tutoring. These days, Qt Creator is a much bigger code base than Designer (by more than an order of magnitude) and is fairly decently architected, so large-scale projects might take inspiration from there.

Related

What is the meaning of "It is possible to map objects in the problem domain to those in the program"?

I have read the above statement in the OOP C++ balagurusamy book. This statement is written under the topic of "benefits of OOP." I have tried to understand this but i am not getting. so can anyone help me to sort out this?
This means that you can associate a relationship(kind of) between real life objects and objects in a program. For example,
EXAMPLE 1
Lets say you have a Factory of different Vehicles and the vehicles have different attributes like name, mfd date, number of tires, size etc etc.
Now you may(can) implement this real life problem(scenario) in a program as follows:
class Vehicle
{
...constructors and other code here
std::string name;
float date;
std::string color;
...//and so on
}
class Car: public Vehicle
{
//here you can add type specific code, that is code that is specific to Car
}
class Factory
{
std::vector<Vehicles> myVehicles;
}
So you're mapping a real life scenario to the domain of the program. Obviously there are many more examples, i have given just one of them.
The reason for doing this is that now you can handle different things about a particular factory and other real life objects. In this case a factory object has a std::vector of Vehicle's which represent the different vehicles that this particular Factory has. Simlarly you can have another factory lets say at Ohio which contain its own vehicle. So basically you are modelling real life problem through a program.
Generally it is all about using basic OOP principals in a scope of a program design. Programs are writing for human needs (domain scope). For example you need to print an essay, and you need a program for typing i.e. text editor. This text editor should have contain some user interface. This user interface can be logically split on some components like: text area, menu, menu times, status line, toolbar, buttons in tool bar, select file dialog for open and save operation etc.
To implement all of those components in your program, you can logically split it on sort of classes which were encapsulate data and code related to each component. As well as those classes expected to have some common behavior like show/hide, move etc. So you can implement it with a common class - widget. Then inherit all component classes from widget parent, so that you don't have to duplicate common code for: show, hide and move operations for all your components.
In menu you have an menu items, like open, save, exit etc. All items are similar so they can be implements by the same class in the same time, when you click on it operation should differ, i.e. open and save should open a select file dialog and then open or save file, when exit should save file and then close application. How to inject this operation inside a class without creating a sub-class for each menu item. You can create an fully virtual abstract class called Action with one fully virtual method, like perform. Then you can add a pointer on reference member into MenuIntem class. Then you can create a classes inherits Action, override fully virtual member - perform. Implementation of this perform will do some exact operation - like open file, safe file etc. And then you can inject reference on implementation into MenuItems objects. I.e. something like this:
struct Bounds {
std::size_t left, top, width, height;
}
class Widget {
Widget(const Widget&) = delete;
Widget& oprator=(const Widget&) = delete;
public:
Widget(Bounds& bounds) noexcept:
bounds_(bounds);
{}
virtual ~Widget() = default;
void move(std::size_t left, std::size_t top)
{
bounds_.left = left;
bounds_.top = top;
draw();
}
virtual void draw() {
// some platform specific implementation
}
virtual void show() {
// some platform specific implementation
}
virtual void hide() {
// some platform specific implementation
}
private:
bounds bounds_;
}
class Window: public Widget {
// some implementation
}
class SelectFileDialog: public Window {
// Some implemenation
}
....
class TextArea:public Widet
{
// some implementation
}
class StatusLine:public Widet
{
// some implementation
}
class Button: public Widget
{
public:
Button(Bounds& bounds, const std::string& caption):
Widget(),
caption_(caption)
{}
virtual void onClick() = 0;
private:
std::string caption_;
}
class Action {
Action(const Action&) = delete;
Action& operator=(Action&) = delete;
public:
virtual ~Action() = default;
virtual void perform() = 0;
}
class MenuItem: public Button {
public:
MenuItem(const std::string& cation, const std::shared_ptr<Action> action):
Button({0,0,100,30},cation),
action_(action)
{}
virtual void onClick() override
{
// real app should use some arguments
action_->perform();
}
private:
std::shared_ptr<Action> action_;
}
class SaveFileAction: public Action {
public:
void perform() override
{
SelectFileDialog dlg;
dlg.show();
// some save implementation
}
}
class OpenFileAction: public Action {
public:
void perform() override
{
SelectFileDialog dlg;
dlg.show();
// some open file implementation
}
}
class PrintFileAction: public Action {
public:
void perform() override
{
// some print file impementation
}
}
....
class MenuBox: public Widget
{
public:
MenuBox(Bounds& bounds):
Widget(bounds)
{}
void addItem(const std::string& caption, std::shared_ptr<Action> action)
{
items_.emplace_back( std::make_shared<MenuItem>( caption, action ) );
}
private:
std::vector< std::shared_ptr<MenuItem> > items_;
}
class MainWinow: public Window
{
public:
MainWidow():
Window({ 100, 100, 640, 480})
{}
void addComponent(const std::shared_ptr<Widget>& w)
{
childs_.epmplace_back(w);
}
virtual void show() override {
Window::show();
for(auto w: childs_) {
w->show();
}
}
pivate:
std::vector< std::shared_ptr<Widgets> > childs_;
}
int main(int argc, const char** argv) {
std::shared_ptr<MainWidow> w( new MainWindow() );
...
std::shared_ptr<MenuBox> mainMenu(new MenuBox() );
mainMenu->addItem("Open", std::shared_ptr<Action>(new OpenFileAction()) );
mainMenu->addItem("Save", std::shared_ptr<Action>(new SaveFileAction()) );
mainMenu->addItem("Print", std::shared_ptr<Action>(new PrintFileAction()) );
mainMenu->addItem("Exit", std::shared_ptr<Action>(new ExitAction()) );
...
w->addComponent(mainMenu);
w->addComponent(textArrea);
w->addComponent(statusLine);
...
w->show();
return 0;
}
Those this explains all basic OPP principles: encapsulation, aggregation, inheritance and polymorphism.
This is how OOP allow you to organize you application structure and help to reuse code. It is not for free i.e. all this abstractions cost memory and CPU time, for some small console programs OOP may not be needed. For most of complex programs benefits gives you a lot.
There are libraries of reusable classes you can use for different domains.
The most common is C++ standard library with io streams, time, threads etc. For other common scenarios you can look into boost
For graphical user interface there are a log Widget libraries like: wxWidgets, QT, GTK MM, Fltk and many others.
For video games there are special libraries and additional programs called engines - for example OGRE, Unreal Engine etc.
As well as there are libraries for implementing WEB and file severs, printing, image processing, AI and so one.
All this frameworks use object oriented programming concept to describe problem domain.

Qt debug error (variable) not accessible

I have encountered a runtime error which while debugging list a variable as not accessible. there are other post on this subject but the fixes do not work for me.
I use QtCreator 4.5.1 and Qt lib 5.10 (Mingw32 5.3, MSVC14 x64, MSVC x64)
I have disabled "Load system GDB pretty printers" as suggested in
disable pretty printers post
.
Intent of failing code:
In order to communicate between my decoupled QtQuick2 viewmodels I implemented a simple version of the EventAgregator pattern.
the implementation is still missing thread safty and clean up of no longer existing subscribers. however it is not relevant for my error.
break point at error to be thrown and current watch list
relevant code files:
qspinstartup.h
class QSpinStartup:QObject
{
public:
QSpinStartup();
bool loadQSpin(const QString dir, const QString startView="");
private:
void registerQmlGui();
void registerQmlViewModel();
QQmlApplicationEngine engine;
//constant viewmodel objects
CodeEditorTab* codeEditor;
VerificationTab* verificationEditor;
QSpinMessageService msgService;
};
qspinstartup.cpp
QSpinStartup::QSpinStartup():QObject()
,verificationEditor(new VerificationTab(msgService,this))
,codeEditor(new CodeEditorTab(msgService,this))
//,console(new ConsoleEditor(msgService,this))
{
registerQmlGui();
registerQmlViewModel();
initPointerObjects();
auto ctx = engine.rootContext();
qSpinNullPtrCheck(ctx);
PrintToConsole p("pri");
msgService.publish<PrintToConsole>(p);
ctx->setContextProperty("codeEditorVm",codeEditor);
}
bool QSpinStartup::loadQSpin(const QString dir,const QString startView)
{
QDir ldir(dir);
QString l = ldir.entryList(QStringList()<<startView,QDir::Files).first();
qInfo()<<l;
engine.load(QUrl(l.prepend("qrc:/")));
bool isReady = engine.rootObjects().count()>0;
qSpinSafeFail(!isReady,"Qml Application failed to start engine");
return isReady;
}
void QSpinStartup::registerQmlGui()
{
QSpinWorkSpace::Workspace::registerAsQml();
}
void QSpinStartup::registerQmlViewModel()
{
VerificationTab::registerAsQml();
CodeEditorTab::registerAsQml();
ConsoleEditor::registerAsQml();
}
ieventbase.h
class IEventBase{
public:
enum Types{ // <--simple way to give an object a type specifier
PrintToConsole,
CurrentDocumentChanged,
CompilerOptions,
SpinArgCommand
};
IEventBase(Types t):type(t){}
const Types type;
};
qspinmessageservice.h
template<class T>
class ISubscriber{
public:
virtual ~ISubscriber(){}
virtual void executeSubscribed(T& event)=0;
};
class QSpinMessageService: public QObject{
Q_OBJECT
typedef QPointer<QObject> subscriber;
typedef QVector<subscriber> EvAgrList;
QHash<IEventBase::Types,EvAgrList> subs;
public:
QSpinMessageService(QObject* parent=nullptr):QObject(parent)
,subs(QHash<IEventBase::Types,EvAgrList>()){}
void subscribe(QObject *obj, IEventBase::Types key){
if(!subs.contains(key)) // <-- this line cause my error
subs[key]= EvAgrList();
EvAgrList& list = subs[key];
list<<subscriber(obj);
}
template<typename T>
void publish(typename T::IEventBase execute){
EvAgrList lSubs = subs[execute.type];
for(int i=0; i<lSubs.count();i++){
subscriber&q = lSubs[i];
if(q.isNull()){
lSubs.removeOne(q);
--i;
continue;
}
ISubscriber<T>* a = dynamic_cast<ISubscriber<T>*>(q.data());
qDebug()<< q<<a;
//ISubscriber<T>* s = dynamic_cast<ISubscriber<T>*>(q);
a->executeSubscribed(static_cast<T&>(execute));
}
}
};
static QSpinMessageService dummy;
verificationtab.cpp (its a big class so i excluded irrelevant parts)
VerificationTab::VerificationTab(QSpinMessageService &service, QObject *parent) :
QSpinTab(parent,"Verification",QSpinTabType::Verification)
,msgService(service)
,m_stateProp(new StateSpaceProp(this)),m_stateSpec(new StateSpaceSpec(this))
,m_memoryUsage(new MemoryUsage(this)),m_settings(new VerificationSettings(this))
{
msgService.subscribe(this,IEventBase::CurrentDocumentChanged); // <-- error call
//if the line above is commented out the application runs
}
Github repository of the full application
note:
I Designed the QSpinMessageService in seperate empty project, before adding the files to the main project. the subcribe methods works in that project.
Any idea of how to fix this?
I finally located the cause of my error. Despite the fact that the Debug Watch list indicate That the QSpinMessageService object exist, It havent been propberly initialized yet at this point.
Why subscribe() is even able to be accessed is beyond my current cpp skills.
The fix:
In qspinstartup.cpp the construtor need to be changed as follows.
qspinstartup.cpp
QSpinStartup::QSpinStartup():QObject()
{
codeEditor = new CodeEditorTab(msgService,this);
verificationEditor = new VerificationTab(&msgService,this);
registerQmlGui();
registerQmlViewModel();
auto ctx = engine.rootContext();
qSpinNullPtrCheck(ctx);
PrintToConsole p("pri");
msgService.publish<PrintToConsole>(p);
ctx->setContextProperty("codeEditorVm",codeEditor);
}

Avoid redundant calls to QSortFilterProxyModel::filterAcceptsRow() if the filter has become strictly narrower

Is there any way invalidate the filter in a QSortFilterProxyModel, but to indicate that the filter has been narrowed down so filterAcceptsRow() should be called only on the currently visible rows?
Currently Qt doesn't do that. When I call QSortFilterProxyModel::invalidateFilter(), and my filter is changed from "abcd" to "abcde", an entirely new mapping is created, and filterAcceptsRow() is called on all source rows, even though it's obvious that source rows that were hidden so far will remain hidden.
This is the code from Qt's sources in QSortFilterProxyModelPrivate::create_mapping() which calls my overridden filterAcceptsRow(), and it creates an entirely new Mapping and iterates over all the source rows:
Mapping *m = new Mapping;
int source_rows = model->rowCount(source_parent);
m->source_rows.reserve(source_rows);
for (int i = 0; i < source_rows; ++i) {
if (q->filterAcceptsRow(i, source_parent))
m->source_rows.append(i);
}
What I want is to iterate only the visible rows in mapping and call filterAcceptsRow() only on them. If a row is already hidden filterAcceptsRow() should not be called on it, because we already know that it would return false for it (the filter has become more stringent, it hasn't been loosened).
Since I have overriden filterAcceptsRow(), Qt can't know the nature of the filter, but when I call QSortFilterProxyModel::invalidateFilter(), I have the information about whether the filter has become strictly narrower, so I could pass that information to Qt if it has a way of accepting it.
On the other hand, if I've changed the filter from abcd to abce, then the filter should be called on all source rows, since it has become strictly narrower.
I wrote a QIdentityProxyModel subclass that stores a list of chained QSortFilterProxyModel. It provides an interface similar to QSortFilterProxyModel and accepts a narrowedDown boolean parameter that indicates if the filter is being narrowed down. So that:
When the filter is being narrowed down, a new QSortFilterProxyModel is appended to the chain, and the QIdentityProxyModel switches to proxy the new filter at the end of the chain.
Otherwise, It deletes all the filters in the chain, constructs a new chain with one filter that corresponds to the current filtering criteria. After that, the QIdentityProxyModel switches to proxy the new filter in the chain.
Here is a program that compares the class to using a normal QSortFilterProxyModel subclass:
#include <QtWidgets>
class FilterProxyModel : public QSortFilterProxyModel{
public:
explicit FilterProxyModel(QObject* parent= nullptr):QSortFilterProxyModel(parent){}
~FilterProxyModel(){}
//you can override filterAcceptsRow here if you want
};
//the class stores a list of chained FilterProxyModel and proxies the filter model
class NarrowableFilterProxyModel : public QIdentityProxyModel{
Q_OBJECT
//filtering properties of QSortFilterProxyModel
Q_PROPERTY(QRegExp filterRegExp READ filterRegExp WRITE setFilterRegExp)
Q_PROPERTY(int filterKeyColumn READ filterKeyColumn WRITE setFilterKeyColumn)
Q_PROPERTY(Qt::CaseSensitivity filterCaseSensitivity READ filterCaseSensitivity WRITE setFilterCaseSensitivity)
Q_PROPERTY(int filterRole READ filterRole WRITE setFilterRole)
public:
explicit NarrowableFilterProxyModel(QObject* parent= nullptr):QIdentityProxyModel(parent), m_filterKeyColumn(0),
m_filterCaseSensitivity(Qt::CaseSensitive), m_filterRole(Qt::DisplayRole), m_source(nullptr){
}
void setSourceModel(QAbstractItemModel* sourceModel){
m_source= sourceModel;
QIdentityProxyModel::setSourceModel(sourceModel);
for(FilterProxyModel* proxyNode : m_filterProxyChain) delete proxyNode;
m_filterProxyChain.clear();
applyCurrentFilter();
}
QRegExp filterRegExp()const{return m_filterRegExp;}
int filterKeyColumn()const{return m_filterKeyColumn;}
Qt::CaseSensitivity filterCaseSensitivity()const{return m_filterCaseSensitivity;}
int filterRole()const{return m_filterRole;}
void setFilterKeyColumn(int filterKeyColumn, bool narrowedDown= false){
m_filterKeyColumn= filterKeyColumn;
applyCurrentFilter(narrowedDown);
}
void setFilterCaseSensitivity(Qt::CaseSensitivity filterCaseSensitivity, bool narrowedDown= false){
m_filterCaseSensitivity= filterCaseSensitivity;
applyCurrentFilter(narrowedDown);
}
void setFilterRole(int filterRole, bool narrowedDown= false){
m_filterRole= filterRole;
applyCurrentFilter(narrowedDown);
}
void setFilterRegExp(const QRegExp& filterRegExp, bool narrowedDown= false){
m_filterRegExp= filterRegExp;
applyCurrentFilter(narrowedDown);
}
void setFilterRegExp(const QString& filterRegExp, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::RegExp);
m_filterRegExp.setPattern(filterRegExp);
applyCurrentFilter(narrowedDown);
}
void setFilterWildcard(const QString &pattern, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::Wildcard);
m_filterRegExp.setPattern(pattern);
applyCurrentFilter(narrowedDown);
}
void setFilterFixedString(const QString &pattern, bool narrowedDown= false){
m_filterRegExp.setPatternSyntax(QRegExp::FixedString);
m_filterRegExp.setPattern(pattern);
applyCurrentFilter(narrowedDown);
}
private:
void applyCurrentFilter(bool narrowDown= false){
if(!m_source) return;
if(narrowDown){ //if the filter is being narrowed down
//instantiate a new filter proxy model and add it to the end of the chain
QAbstractItemModel* proxyNodeSource= m_filterProxyChain.empty()?
m_source : m_filterProxyChain.last();
FilterProxyModel* proxyNode= newProxyNode();
proxyNode->setSourceModel(proxyNodeSource);
QIdentityProxyModel::setSourceModel(proxyNode);
m_filterProxyChain.append(proxyNode);
} else { //otherwise
//delete all filters from the current chain
//and construct a new chain with the new filter in it
FilterProxyModel* proxyNode= newProxyNode();
proxyNode->setSourceModel(m_source);
QIdentityProxyModel::setSourceModel(proxyNode);
for(FilterProxyModel* node : m_filterProxyChain) delete node;
m_filterProxyChain.clear();
m_filterProxyChain.append(proxyNode);
}
}
FilterProxyModel* newProxyNode(){
//return a new child FilterModel with the current properties
FilterProxyModel* proxyNode= new FilterProxyModel(this);
proxyNode->setFilterRegExp(filterRegExp());
proxyNode->setFilterKeyColumn(filterKeyColumn());
proxyNode->setFilterCaseSensitivity(filterCaseSensitivity());
proxyNode->setFilterRole(filterRole());
return proxyNode;
}
//filtering parameters for QSortFilterProxyModel
QRegExp m_filterRegExp;
int m_filterKeyColumn;
Qt::CaseSensitivity m_filterCaseSensitivity;
int m_filterRole;
QAbstractItemModel* m_source;
QList<FilterProxyModel*> m_filterProxyChain;
};
//Demo program that uses the class
//used to fill the table with dummy data
std::string nextString(std::string str){
int length= str.length();
for(int i=length-1; i>=0; i--){
if(str[i] < 'z'){
str[i]++; return str;
} else str[i]= 'a';
}
return std::string();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//set up GUI
QWidget w;
QGridLayout layout(&w);
QLineEdit lineEditFilter;
lineEditFilter.setPlaceholderText("filter");
QLabel titleTable1("NarrowableFilterProxyModel:");
QTableView tableView1;
QLabel labelTable1;
QLabel titleTable2("FilterProxyModel:");
QTableView tableView2;
QLabel labelTable2;
layout.addWidget(&lineEditFilter,0,0,1,2);
layout.addWidget(&titleTable1,1,0);
layout.addWidget(&tableView1,2,0);
layout.addWidget(&labelTable1,3,0);
layout.addWidget(&titleTable2,1,1);
layout.addWidget(&tableView2,2,1);
layout.addWidget(&labelTable2,3,1);
//set up models
QStandardItemModel sourceModel;
NarrowableFilterProxyModel filterModel1;;
tableView1.setModel(&filterModel1);
FilterProxyModel filterModel2;
tableView2.setModel(&filterModel2);
QObject::connect(&lineEditFilter, &QLineEdit::textChanged, [&](QString newFilter){
QTime stopWatch;
newFilter.prepend("^"); //match from the beginning of the name
bool narrowedDown= newFilter.startsWith(filterModel1.filterRegExp().pattern());
stopWatch.start();
filterModel1.setFilterRegExp(newFilter, narrowedDown);
labelTable1.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
stopWatch.start();
filterModel2.setFilterRegExp(newFilter);
labelTable2.setText(QString("took: %1 msecs").arg(stopWatch.elapsed()));
});
//fill model with strings from "aaa" to "zzz" (17576 rows)
std::string str("aaa");
while(!str.empty()){
QList<QStandardItem*> row;
row.append(new QStandardItem(QString::fromStdString(str)));
sourceModel.appendRow(row);
str= nextString(str);
}
filterModel1.setSourceModel(&sourceModel);
filterModel2.setSourceModel(&sourceModel);
w.show();
return a.exec();
}
#include "main.moc"
Notes:
The class provides some kind of optimization only when the filter is being narrowed down, since the newly constructed filter at the end of the chain does not need to search through all source model's rows.
The class depends on the user to tell if the filter is being narrowed down. That is when the user passes true for the argument narrowedDown, the filter is assumed to be a special case of the current filter (even if it is not really so). Otherwise, it behaves exactly the same as the normal QSortFilterProxyModel and possibly with some additional overhead (resulted from cleaning up the old filter chain).
The class can be further optimized when the filter is not being narrowed down, so that it looks in the current filter chain for a filter that is similar to the current filter and switch to it immediately (instead of deleting the whole chain and starting a new one). This can be particularly useful when the user is deleting some characters at the end filter QLineEdit (ie. when the filter changes back from "abcd" to "abc", since you should already have a filter in the chain with "abc"). But currently, this is not implemented as I want the answer to be as minimal and clear as possible.
Because the filters can also be generic (for custom filter sorting you are encouraged to override filterAcceptsRow()) the ProxyModel cannot know whether it will become narrower or not.
if you would need to provide it to the proxy as a parameter it would break encapsulation because the filter-logic should only be contained inside the filter model.
You cannot override invalidateFilter though because it is not declared virtual. What you can do is having a structure in your derived proxy where you store the values you lastly filtered in there and only check them in , when the filter just got narrower. Both of this you can do in filterAcceptsRow().
invalidateFilter() still will call rowCount() though. So this function needs to have a low call time in your model for this to be effective.
Here is some pseudocode how filterAcceptsRow() could look like:
index // some index to refer to the element;
if(!selectionNarrowed()) //need search all elements
{
m_filteredElements.clear(); //remove all previously filtered
if(filterApplies(getFromSource(index))) //get element from sourceModel
{
m_filteredElements.add(index); //if applies add to "cache"
return true;
}
return false;
}
//selection has only narrowed down
if(!filterApplies(m_filteredElements(index)) //is in "cache"?
{
m_filteredElements.remove(index); //if not anymore: remove from cache
return false;
}
return true;
There are some things to be aware of though. Be careful if you want to store the QModelIndex..You can have a look at QPersistentModelIndex.
You also need to be aware about changes in the underlying model and connect the appropriate slots and invalidate your "cache" in those cases.
While an alternative could be "filter stacking". I see this may get confusing when you really need to invalidate all filters.

How to specify different strings for a QComboBox's popup menu than are displayed in the box itself?

I've got a Qt/C++ app that contains a number of (non-editable) QComboBox widgets in a crowded window. The user can click on these combo-box to choose from various types of units. To keep the window width manageable, the QComboBoxes need to be as skinny as possible.
Conceptually, the available unit types in the combobox are these:
feet
meters
milliseconds
[etc]
... but to allow the QComboBoxes to be as skinny as possible, they are represented on-screen by abbreviations, e.g.:
f
m
mS
[etc]
... so far, so good, but what management would like now is to have the non-abbreviated strings ("feet", "meters", "milliseconds", etc) show up in the popup-menu that appears when the user clicks on the QComboBox... while retaining the abbreviated form in the box itself. This seems logically doable (since the popup-menu appears only briefly, in front of the rest of the GUI, there's no fundamental reason it cannot be made wider), but it's not clear to me how to implement this using QComboBox.
Is there a "right way" to do this (short of hacking the Qt combobox code)?
A delegate would be simpler:
class Delegate : public QStyledItemDelegate {
public:
QString displayText(const QVariant & value, const QLocale & locale) const override {
// value contains one of your short labels {"mS", "f", "m"}
return /* Just return the corresponding long label */;
}
};
And
yourComboBox->setItemDelegate(new Delegate);
Lacking a nice way to do this, I found an ugly way to do it... the following code seems to work well enough for me under MacOS/X and Windows, at least. (I haven't tried it under other OS's but I expect it would work elsewhere as well):
#include <QComboBox>
#include <QIdentityProxyModel>
#include <QStandardItemModel>
#include <QProxyStyle>
static const char * shortLabels[] = {"mS", "f", "m", [... and so on...] };
static const char * longLabels[] = {"milliseconds", "feet", "meters", [... and so on...] };
// A wrapper-facade data-model object that will provide the long label strings, but only when appropriate
class LongLabelsProxyModel : public QIdentityProxyModel
{
public:
LongLabelsProxyModel(QAbstractItemModel * source, QObject * parent = 0) : QIdentityProxyModel(parent), forceShortLabelsCounter(0)
{
setSourceModel(source);
}
virtual QVariant data(const QModelIndex &index, int role) const
{
return ((forceShortLabelsCounter == 0)&&((role == Qt::DisplayRole)||(role == Qt::EditRole)))
? QVariant(QString(longLabels[index.row()])) :
QIdentityProxyModel::data(index, role);
}
void BeginForceShortNames() {forceShortLabelsCounter++;}
void EndForceShortNames() {forceShortLabelsCounter--;}
private:
int forceShortLabelsCounter;
};
#ifndef __APPLE__
// Under MacOS/X, this class isn't necessary, because OS/X uses a native/non-Qt popup widget.
// For other OS's, however, we need to make the Qt popup widget wider so the long labels can be fully visible
class WiderPopupProxyStyle : public QProxyStyle
{
public:
WiderPopupProxyStyle(QStyle * baseStyle) : QProxyStyle(baseStyle) {/* empty */}
virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const
{
QRect r = QProxyStyle::subControlRect(cc, option, sc, widget);
if ((cc == QStyle::CC_ComboBox)&&(sc == QStyle::SC_ComboBoxListBoxPopup)) r.setWidth(r.width()*2);
return r;
}
};
#endif
// My customized combo-box class that will show long strings in the popup menu
class DelayUnitsComboBox : public EnumComboBoxComponent
{
public:
DelayUnitsComboBox(QWidget * parent = NULL ) : QComboBox(parent)
{
#ifndef __APPLE__
// otherwise the popup list is the same width as the combo box
// itself, which isn't wide enough to see the long strings
setStyle(new WiderPopupProxyStyle(style()));
#endif
setModel(new LongLabelsProxyModel(new QStandardItemModel(0, 1, this), this));
// Officially populate the QComboBox with the short labels
for (int i=0; i<((sizeof(shortLabels)/sizeof(shortLabels[0])); i++) addItem(shortLabels[i]);
}
// overridden so that when drawing the actual QComboBox, we still use the shorter names
virtual void paintEvent(QPaintEvent * p)
{
LongLabelsProxyModel * llpm = static_cast<LongLabelsProxyModel*>(model());
llpm->BeginForceShortNames();
EnumComboBoxComponent::paintEvent(p);
llpm->EndForceShortNames();
}
};

Override VCL class/component protected method - How to code and use?

I am rewriting old existing code and I'm giving the icons an overhaul. I used to have bitmaps assigned to TMenuItems but I'm changing that in favor of ImageIndex and a TImageList with colordepth 32bit, containing icons with an alpha channel. The ImageList is created and populated with icons at design time. The ImageIndex are assigned during program startup and changed if/when appropriate.
I noticed that when a MenuItem is disabled (enabled = false), the resulting image doesn't look great (at all) and I read that this is due to VCL. Mentioned link also links to Delphi code that can convert an icon to its greyscale values.
I'm not fluent in Delphi nor changing VCL components, subclassing them, inheriting from them etc. I normally simply use what is available without changing it. so I'm starting with some basic questions:
Here's a very simple attempt to inherit from TImage and override DoDraw(), to make it never disable the icon in the first place (decipering the Delphi code to greyscale can be done in a second step)
class MyTImageList : public TImageList
{
public:
__fastcall MyTImageList(Classes::TComponent* AOwner)
: TImageList(AOwner) {} ;
virtual __fastcall DoDraw(int Index, TCanvas *Canvas, int X, int Y, unsigned int Style, bool Enabled = true)
{
return TImageList::DoDraw(Index, Canvas, X, Y, Style) ;
}
};
FYI: I use C++ Builder 2009
It does not compile, error: [BCC32 Error] Main.h(1018): E2113 Virtual function '_fastcall TMainForm::MyTImageList::DoDraw(int,TCanvas *,int,int,unsigned int,bool)' conflicts with base class 'TCustomImageList'
Since I'm very insecure about inheriting from VCL component classes I'm not sure if I'm dealing with a typo or something very constructively wrong ? Kindly enlighten me.
Assuming this compiles I'm actually not sure how to proceed further either.
Because the ImageList is created at design time, and used throughout the code. For this change to work I have to work with 'MyTimageList'.
So, do I create MyTimageList during Form construction and Assign() the content of the design-time-ImageList, or is there a more efficient way to avoid copying over everything ?
Actually, thinking about the latter question more, I could simply use the internal ImageList of the design time Imagelist instance.
Here is a C++ translation of the Delphi code:
class MyTImageList : public TImageList
{
protected:
virtual void __fastcall DoDraw(int Index, Graphics::TCanvas *Canvas, int X, int Y, unsigned Style, bool Enabled = true);
public:
__fastcall MyTImageList(Classes::TComponent* AOwner, TImageList *DesignImageList);
};
__fastcall MyTImageList::MyTImageList(Classes::TComponent* AOwner, TImageList *DesignImageList)
: TImageList(AOwner)
{
ColorDepth = DesignImageList->ColorDepth;
Handle = DesignImageList->Handle; // Use the internally kept List of the design time ImageList
ShareImages = true;
}
unsigned __fastcall GetRGBColor(TColor Value)
{
unsigned Result = ColorToRGB(Value);
switch (Result)
{
case clNone: Result = CLR_NONE; break;
case clDefault: Result = CLR_DEFAULT; break;
}
return Result;
}
void __fastcall MyTImageList::DoDraw(int Index, TCanvas *Canvas, int X, int Y, unsigned Style, bool Enabled)
{
if ((Enabled) || (ColorDepth != cd32Bit))
{
TImageList::DoDraw(Index, Canvas, X, Y, Style, Enabled);
}
else if (HandleAllocated())
{
IMAGELISTDRAWPARAMS Options = {0};
Options.cbSize = sizeof(Options);
Options.himl = (HIMAGELIST) Handle;
Options.i = Index;
Options.hdcDst = Canvas->Handle;
Options.x = X;
Options.y = Y;
Options.cx = 0;
Options.cy = 0;
Options.xBitmap = 0;
Options.yBitmap = 0;
Options.rgbBk = GetRGBColor(BkColor);
Options.rgbFg = GetRGBColor(BlendColor);
Options.fStyle = Style;
Options.fState = ILS_SATURATE; // Grayscale for 32bit images
ImageList_DrawIndirect(&Options);
}
}
OK, the answer is simple, and it needed some trial and error to get there, because the documentation I had access to was not clear about that. (E.g. also wrong about the TCanvas pointer).
The function/method returns void. THAT's what was missing in the originally posted code and what caused the error. I got following code to work nicely.
class MyTImageList : public TImageList
{
public:
__fastcall MyTImageList(Classes::TComponent* AOwner, TImageList *DesignImageList)
: TImageList(AOwner)
{
Handle = DesignImageList->Handle ; // Use the internally kept List of the design time ImageList
ShareImages = true;
}
protected:
virtual __fastcall void DoDraw(int Index, TCanvas *Canvas, int X, int Y, unsigned int Style, bool Enabled = true)
{
return TImageList::DoDraw(Index, Canvas, X, Y, Style, true /*Enabled*/) ; // Always draw enabled
}
};
This issue is closed.