Upload
others
View
13
Download
0
Embed Size (px)
Citation preview
Developing BornAgain graphical user interface:lessons learned
Gennady PospelovScientific Computing Group at MLZ
Workshop on Neutron Scattering Data Analysis Software6-8 June, 2018, Sorangna
Developing graphical user interface
○ Design of the visual composition of an application○ Design of internal application structure
We are going to focus on aspects of internal design of large GUI applications, leaving questions of usability and user experience aside.
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 02
Outline
○ Context○ GUI design patterns○ Model/View in Qt○ BornAgain application model○ Useful recipes
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 03
Context
GUI application in most cases deals with three copies of the data
Record state
Session state
Screen state
The data stored on disk or in the database
Temporary local version of data that the user works on until they save
Copy of data laying in GUI components which users see on the screen
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 04
Context
Record state
Session state
Screen state
The data stored on disk or in the database
Temporary local version of data that the user works on until they save
Copy of data laying in GUI components which users see on the screen
Core state
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 04
GUI application in most cases deals with three copies of the data
Context
Session state
Screen state
Temporary local version of data that the user works on until they save
Copy of data laying in GUI components which users see on the screen
How to organize presentation and presentation-related code?
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 05
Context
Session state
Screen state
How to organize presentation and presentation-related code?
Temporary local version of data that the user works on until they save
Copy of data laying in GUI components which users see on the screen
● It is important to keep session state and screen state separated● Session objects should be self contained and work without reference to the presentation
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 05
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 06
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 07
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Widget::Widget(QWidget* parent) : QDialog(parent) , m_number(0) , m_lineEdit(new QLineEdit) , m_label(new QLineEdit){ auto layout = new QVBoxLayout; layout->addWidget(m_lineEdit); layout->addWidget(m_label);
setLayout(layout);
m_lineEdit->setText(QString::number(m_number));
connect(m_lineEdit, &QLineEdit::textChanged, this, &Widget::onTextChanged);}
void Widget::onTextChanged(){ if (m_lineEdit->text().toInt() == 42) { m_label->setText("Good!"); m_number = m_lineEdit->text().toInt(); }}
int main(){ Widget widget; widget.exec(); std::cout << "Number was " << widget.getNumber() << std::endl;}
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 07
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Widget::Widget(QWidget* parent) : QDialog(parent) , m_number(0) , m_lineEdit(new QLineEdit) , m_label(new QLineEdit){ auto layout = new QVBoxLayout; layout->addWidget(m_lineEdit); layout->addWidget(m_label);
setLayout(layout);
m_lineEdit->setText(QString::number(m_number));
connect(m_lineEdit, &QLineEdit::textChanged, this, &Widget::onTextChanged);}
void Widget::onTextChanged(){ if (m_lineEdit->text().toInt() == 42) { m_label->setText("Good!"); m_number = m_lineEdit->text().toInt(); }}
int main(){ Widget widget; widget.exec(); std::cout << "Number was " << widget.getNumber() << std::endl;}
Data (Model)
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 08
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Widget::Widget(QWidget* parent) : QDialog(parent) , m_number(0) , m_lineEdit(new QLineEdit) , m_label(new QLineEdit){ auto layout = new QVBoxLayout; layout->addWidget(m_lineEdit); layout->addWidget(m_label);
setLayout(layout);
m_lineEdit->setText(QString::number(m_number));
connect(m_lineEdit, &QLineEdit::textChanged, this, &Widget::onTextChanged);}
void Widget::onTextChanged(){ if (m_lineEdit->text().toInt() == 42) { m_label->setText("Good!"); m_number = m_lineEdit->text().toInt(); }}
int main(){ Widget widget; widget.exec(); std::cout << "Number was " << widget.getNumber() << std::endl;}
Data (Model)
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 08
View
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Widget::Widget(QWidget* parent) : QDialog(parent) , m_number(0) , m_lineEdit(new QLineEdit) , m_label(new QLineEdit){ auto layout = new QVBoxLayout; layout->addWidget(m_lineEdit); layout->addWidget(m_label);
setLayout(layout);
m_lineEdit->setText(QString::number(m_number));
connect(m_lineEdit, &QLineEdit::textChanged, this, &Widget::onTextChanged);}
void Widget::onTextChanged(){ if (m_lineEdit->text().toInt() == 42) { m_label->setText("Good!"); m_number = m_lineEdit->text().toInt(); }}
int main(){ Widget widget; widget.exec(); std::cout << "Number was " << widget.getNumber() << std::endl;}
Data (Model)
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 08
View
Business logic
class Widget : public QDialog { Q_OBJECTpublic: Widget(QWidget* parent = nullptr); ~Widget();
int getNumber() { return m_number;}
private slots: void onTextChanged();
private: int m_number; QLineEdit* m_lineEdit; QLineEdit* m_label;};
Widget::Widget(QWidget* parent) : QDialog(parent) , m_number(0) , m_lineEdit(new QLineEdit) , m_label(new QLineEdit){ auto layout = new QVBoxLayout; layout->addWidget(m_lineEdit); layout->addWidget(m_label);
setLayout(layout);
m_lineEdit->setText(QString::number(m_number));
connect(m_lineEdit, &QLineEdit::textChanged, this, &Widget::onTextChanged);}
void Widget::onTextChanged(){ if (m_lineEdit->text().toInt() == 42) { m_label->setText("Good!"); m_number = m_lineEdit->text().toInt(); }}
int main(){ Widget widget; widget.exec(); std::cout << "Number was " << widget.getNumber() << std::endl;}
Data (Model)
Toy dialog
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 08
View
Business logic
Presentation logic
View1 View2
Two toy views
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 09
○ Context○ GUI design patterns○ Model/View in Qt○ BornAgain application model○ Useful recipes
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 10
Model View Controller
The data (model), user interface (view) and interactions (controller) are separated
Disadvantages○ View is tightly coupled to the model and pollutes business logic○ Hard to test a view
Advantages○ Increased flexibility and reuse○ Same data can be displayed in many views
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 11
Model View Controller
Different descriptions contradict each other
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 12
Model
Model View Presenter
Presenter
View
User
Advantages○ Separates view from the model.○ Solves the problem of unit testing of the view.
○ Business logic layer
○ Reads model○ Drives View through interface○ Contains Presentation logic○ Have two concerns: updating
model and presenting model
○ Obeys IView interface○ Very thin○ It is acceptable not to Unit test it
3) command to update 5) query4) change
notification
6) manipulates2) notifies
1) interacts 7) displays
View interface
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 13
Model
Model View ViewModel
ViewModel
View
User
3) command to update 5) query4) change
notification
6) bindings2) notifies
1) interacts 7) displays
Advantages○ Separates view from the model.○ Solves the problem of unit testing of the view.
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 14
○ Business logic layer
○ Reads model○ Drives View through interface○ Contains Presentation logic○ Have two concerns: updating
model and presenting model
○ Obeys IView interface○ Very thin○ It is acceptable not to Unit test it
○ Context○ GUI Design patterns○ Model/View in Qt○ BornAgain application model○ Useful recipes
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 15
Model View in Qt
○ Data contains raw, GUI unaware data
○ Model communicates with data source and provides interface with other components
○ View displays data obtained from the model and handles user input with the help of delegate
○ Delegate renders the data in a view and communicates with the model
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 16
Model View in Qt
○ Data → Model
○ Model → ViewModel or Presenter
○ View
○ Delegate → Controller
Some users insist that the naming is wrong and should be…
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 17
Model View in Qt
It’s easier to think of Qt’s Model View Framework as a way to adapt user data for Qt’s standard widgets.
○ Data → Model
○ Model → ViewModel or Presenter
○ View
○ Delegate → Controller
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 17
Some users insist that the naming is wrong and should be…
○ Context○ GUI Design patterns○ Model/View in Qt○ BornAgain application model○ Useful recipes
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 18
C++ coreC++ core
External dependencies:Eigen, fftw3, GSL
External dependencies:Eigen, fftw3, GSL
Python bindings
StandaloneGUI
StandaloneGUI
External dependencies:Qt5
External dependencies:Qt5
UserUser
script.pyscript.py
BornAgain GUI
Core is Qt independent and fully unaware of GUI existenceGUI objects do not inherit from Core objects, do not contain any Core members
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 19
80k loc60k loc
GUI and Core relations
Time
Sample constructionfully isolated from Core
GUI
CORE
Generates Core simulation,runs it in non-GUI thread
#include “core/simulation.h”s = new Simulation()
s->runSimulation()
Knows how to retrieve results
s→result()delete s
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 20
Core is Qt independent and fully unaware of GUI existenceGUI objects do not inherit from Core objects, do not contain any Core members
BornAgain GUI application model
We use Model-View-ViewModel
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 21
Data Model
View ModelQAbstractItemModel
ViewQAbstractItemView
Data Model○ Key component of BornAgain GUI○ Contains all the data of GUI session○ Contain all the business logic of GUI session○ Can read/write itself on disk
ViewModel○ Adapts data model to Qt’s QAbstractItemModel interface
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
BornAgain GUI application model
QTreeView QListView QTreeView QTreeView Custom widgetQGraphicsSceneCustom widget
Delegate Delegate Delegate
Proxy ModelQAbstractProxyModel
Proxy ModelQFilterProxyModel
Widget MapperQDataWidgetMapper
View ModelQAbstractItemModel
Data Model
queryNotifies
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 22
We use Model-View-ViewModel ... more or less
root
class SessionItem {
public: SessionItem(QString modelType);
private: QMap<Role, QVariant> m_data; QVector<SessionItem*> m_children;};
BornAgain data model
Data model is a tree structure made upof nodes - SessionItem objects
○ Node might contain arbitrary amount of data○ Node might contain row of children○ Node owns its children
Data Model
View Model
View
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 23
SessionItem and its roles
Role QVariant(...)
DataRole Anything
DisplayRole QString
ModelType QString
ToolTipRole QString
RealLimitsRole RealLimits
EditorRole QString
. . . . . .
class SessionItem {
public: SessionItem(QString modelType);
QVariant data(Role role = DataRole) { return m_data[role]; }
private: QMap<Role, QVariant> m_data; QVector<SessionItem*> m_children;};
SessionItem’s m_data is a map where integer Role corresponds to QVariantSet of available roles may differ from node to node
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 24
SessionItem as a base class
All objects of GUI session are derived from SessionItem
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 25
class CylinderItem : public SessionItem {public: CylinderItem() : SessionItem("Cylinder") { addProperty("Radius", 8.0)->setLimits(RealLimits::positive()); addProperty("Height", 16.0)->setToolTip("Height of the cylinder in nanometer") .setEditorType(Constants::ScientificEditorType); } };
SessionItem as a base class
SessionItem
SessionItem
SessionItem
Construction of concrete CylinderItem object leads to creation of the branch in data treeCylinderItem cylinder;
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 25
class CylinderItem : public SessionItem {public: CylinderItem() : SessionItem("Cylinder") { addProperty("Radius", 8.0)->setLimits(RealLimits::positive()); addProperty("Height", 16.0)->setToolTip("Height of the cylinder in nanometer") .setEditorType(Constants::ScientificEditorType); } };
All objects of GUI session are derived from SessionItem
SessionItem as a base class
Role QVariant(...)
DisplayRole Cylinder
Role QVariant(...)
DataRole 8.0
DisplayRole Radius
RealLimitsRole Positive
Role QVariant(...)
DataRole 16.0
DisplayRole Height
ToolTipRole Height of cylinder
EditorRole Scientific Double
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 25
Construction of concrete CylinderItem object leads to creation of the branch in data treeCylinderItem cylinder;
class CylinderItem : public SessionItem {public: CylinderItem() : SessionItem("Cylinder") { addProperty("Radius", 8.0)->setLimits(RealLimits::positive()); addProperty("Height", 16.0)->setToolTip("Height of the cylinder in nanometer") .setEditorType(Constants::ScientificEditorType); } };
SessionItem
SessionItem
SessionItem
All objects of GUI session are derived from SessionItem
○ Adapts data model to QAbstractItemModel interface○ Contains set of methods to manipulate underlying data
BornAgain ViewModel
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 26
Data Model
View Model
View
class ViewModel : public QAbstractItemModel {public: ViewModel(QObject* parent);
// Overloaded methods QVariant data(const QModelIndex& index, int role) const; int rowCount(const QModelIndex& parent) const; int columnCount(const QModelIndex& parent) const;
// Methods to manipulate underlying data model SessionItem* itemFromIndex(const QModelIndex& index); QModelIndex indexFromItem(SessionItem* item); SessionItem* insertItem(const QModelIndex& parent, QString modelType);
private: SessionItem* m_rootItem;};
class ViewModel : public QAbstractItemModel {public: ViewModel(QObject* parent);
// Overloaded methods QVariant data(const QModelIndex& index, int role) const; int rowCount(const QModelIndex& parent) const; int columnCount(const QModelIndex& parent) const;
// Methods to manipulate underlying data model SessionItem* itemFromIndex(const QModelIndex& index); QModelIndex indexFromItem(SessionItem* item); SessionItem* insertItem(const QModelIndex& parent, QString modelType);
private: SessionItem* m_rootItem;};
○ Adapts data model to QAbstractItemModel interface○ Contains set of methods to manipulate underlying data
BornAgain ViewModel
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 26
Data Model
View Model
View
Entry point to underlying data model
BornAgain ViewModel and its Views
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 27
BornAgain ViewModel and its Views
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 27
○ Context○ GUI Design patterns○ Model/View in Qt○ BornAgain application model○ Useful recipes
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 28
QAbstractItemDelegate
○ Provides display and editing facilities for data item from the model○ Should be used if item contains custom QVariant○ Or if native display/editing is not fancy enough
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 29
Data Model
View Model
View
Delegate
QAbstractItemDelegate
○ Provides display and editing facilities for data item from the model○ Should be used if item contains custom QVariant○ Or if native display/editing is not fancy enough
SampleModel model;SessionModelDelegate delegate;
auto tree = new QTreeView;tree->setModel(&model);tree->setItemDelegate(&delegate);
class SessionModelDelegate : public QStyledItemDelegate{public: SessionModelDelegate(QObject* parent);
void paint(QPainter* painter, const QModelIndex& index); QWidget* createEditor(QWidget* parent, const QModelIndex& index);
};
Usage:
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 29
Data Model
View Model
View
Delegate
Using QDataWidgetMapper
Provides automatic data-binding between custom view and model parts
QTreeView displaying sample model Custom layout displaying parts of the model
SampleModel model;
// index pointing to some model partQModelIndex index = model.index(/*row*/0, /*col*/0, QModelIndex());
auto editor = new QDoubleSpinBox;auto mapper = new QDataWidgetMapper;
mapper->setCurrentModelIndex(index);mapper->addMapping(editor);
Any change in the model will be automatically propagated to the widget and back
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 30
Business logic
○ Most of business logic is handled by data model and can be easily tested
Total particle density should be disabled, when there is 2D interference function attached to ParticleLayout
ParticleLayoutItem::ParticleLayoutItem() : SessionGraphicsItem(Constants::ParticleLayoutType){
mapper()->setOnChildrenChange([this](SessionItem*) { updateDensityAppearance(); updateDensityValue(); });}
In BornAgain data model this is handled by setting proper callbackin ParticleLayoutItem constructor
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 31
Unit testing
TEST_F(TestParticleLayoutItem, densityAppearance){ SampleModel model; auto layout = dynamic_cast<ParticleLayoutItem*>(model.insertNewItem(Constants::ParticleLayoutType));
// empty layout should have TotalDensity enabled EXPECT_TRUE(layout->getItem(ParticleLayoutItem::P_TOTAL_DENSITY)->isEnabled());
// adding radial paracrystal shouldn't change it auto interference = model.insertNewItem(Constants::InterferenceFunctionRadialParaCrystalType, model.indexOfItem(layout), -1, ParticleLayoutItem::T_INTERFERENCE); EXPECT_TRUE(layout->getItem(ParticleLayoutItem::T_INTERFERENCE) == interference); EXPECT_TRUE(layout->getItem(ParticleLayoutItem::P_TOTAL_DENSITY)->isEnabled());
// removing paracrystal, TotalDensity still enabled layout->takeRow(ParentRow(*interference)); EXPECT_TRUE(layout->getItem(ParticleLayoutItem::T_INTERFERENCE) == nullptr); EXPECT_TRUE(layout->getItem(ParticleLayoutItem::P_TOTAL_DENSITY)->isEnabled()); delete interference;
// adding 2d interference, TotalDensity should be disabled interference = model.insertNewItem(Constants::InterferenceFunction2DLatticeType, model.indexOfItem(layout), -1, ParticleLayoutItem::T_INTERFERENCE); EXPECT_FALSE(layout->getItem(ParticleLayoutItem::P_TOTAL_DENSITY)->isEnabled());
// removing 2D interference, TotalIntensity should be reenabled layout->takeRow(ParentRow(*interference)); delete interference; EXPECT_TRUE(layout->getItem(ParticleLayoutItem::T_INTERFERENCE) == nullptr); EXPECT_TRUE(layout->getItem(ParticleLayoutItem::P_TOTAL_DENSITY)->isEnabled());}
○ There are 189 tests for the moment
Demonstrates how to test SurfaceDensity enabled/disabledBornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 32
○ Various sources use terms Model, View, Controller, Presenter for different things
○ The common idea behind – separation of concerns○ Qt Model/View has not much to do with structuring of the whole app○ It just provides the way to show user data in Qt’s tables and trees
Summary
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 33
○ Data Model is the core of large GUI application○ Contains all the GUI data○ Provides business logic, covered by unit tests○ Should serialize itself on disk○ Should provide project file back compatibility○ By design have to have undo/redo capabilities○ Be Qt independent, if possible
Summary
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 34
Backup
Links
Stack Overflow: why Qt is misusing model/view terminologyhttps://stackoverflow.com/questions/5543198/why-qt-is-misusing-model-view-terminology
Martin FowlerEssay on GUI Architectureshttps://martinfowler.com/eaaDev/uiArchs.html
Stack Overflow: what are MVP and MVChttps://stackoverflow.com/questions/2056/what-are-mvp-and-mvc-and-what-is-the-difference
Combining MVC, MVP and MVVMhttp://gexiaoguo.github.io/MVC,-MVP-and-MVVM/ Yet another discussion MVC, MVP and MVVMhttps://stackoverflow.com/questions/19444431/what-is-difference-between-mvc-mvp-mvvm-design-pattern-in-terms-of-coding-c-s
QIdentityProxyModel
○ QIdentityProxyModel proxies its source model unmodified○ Allows to substitute data with something else
class SessionDecorationModel : public QIdentityProxyModel{public: SessionDecorationModel(SessionModel* model); ~SessionDecorationModel();
QVariant data(const QModelIndex& index, int role) const { if (role == Qt::DecorationRole) { QVariant result = createIcon(index); if (result.isValid()) return result; }
return QVariant(); }
private: SessionModel* m_model;};
SampleModel model;SessionDecorationModel decoration(&model);
auto list = new QListView;list->setModel(&decoration);
Usage:
QFilterProxyModel
○ Provides support for filtering data passed between model and view○ Can be used to restructure the data for a view without transformation of underlying data
Original model Filtered model
QAbstractProxyModel○ Allows to restructure layout of model in a view without actual change of underlying
layout○ Can be used, for example, to substitute parentship, or change number of columns
Original model Model transformation
Parent was hidden
Radius and Height got another parent
QAbstractProxyModel○ Allows to restructure layout of model in a view without actual change of underlying
layout○ Can be used, for example, to substitute parentship, or change number of columns
Original model Model transformation
Layout of original model was changedncol=2 → ncol=5
BornAgain / GUI SINE2020 Workshop, 6-8 June, 2018, Soragna 31
Unit testing
○ We rely on googletest instead of native QtTest framework○ Signal/slots are tested via QSignalSpy
ProjectDocument document;
QSignalSpy spyDocument(&document, SIGNAL(modified())); EXPECT_EQ(spyDocument.count(), 0);
// Changing document and checking its status modify_models(&models); EXPECT_TRUE(document.isModified()); EXPECT_EQ(spyDocument.count(), 1);
// Saving document document.save(projectFileName); EXPECT_FALSE(document.isModified()); EXPECT_EQ(spyDocument.count(), 2); // save triggers 'modified' signal
Third party Qt code in BornAgain
QCustomPlot, 36k loc
DetailsWidget (Qt creator), 800 loc
Widgetbox (Qt creator), 8k loc
Manhattan style (Qt creator), 7k loc
Gradients, MainWindow layout, 1 pixel splitters
4K displays
○ 15,6” laptops nowadays come mostly in two resolutions
FHD, also known as 1080p, 1920 x 1080, PPI=141UHD-1, also known as 2160p, 3840 x 2160, PPI=282
○ Users then change scale factor in OS settings from 100% to 200%
OS icons, interface fonts etc remains same size as in FHD caseThey start to look visibly crisper
Time on battery decreases by factor of 2 (6 hours → 3 hours)Many application not supporting scaling are unusable
BornAgain in 4K
Will be ready soon...