Rework timelines to avoid GUI crashes when changing conversation.

This commit is contained in:
Julien Wadel 2022-09-19 14:41:38 +02:00
parent f894631885
commit bf9c76a02c
10 changed files with 133 additions and 48 deletions

View file

@ -53,8 +53,32 @@ TimelineListModel::TimelineListModel (QObject *parent) : ProxyListModel(parent)
updateTimelines ();
}
TimelineListModel::TimelineListModel(const TimelineListModel* model){
mSelectedCount = model->mSelectedCount;
CoreHandlers* coreHandlers= CoreManager::getInstance()->getHandlers().get();
connect(coreHandlers, &CoreHandlers::chatRoomStateChanged, this, &TimelineListModel::onChatRoomStateChanged);
connect(coreHandlers, &CoreHandlers::messagesReceived, this, &TimelineListModel::update);
connect(coreHandlers, &CoreHandlers::messagesReceived, this, &TimelineListModel::updated);
QObject::connect(coreHandlers, &CoreHandlers::callStateChanged, this, &TimelineListModel::onCallStateChanged);
QObject::connect(coreHandlers, &CoreHandlers::callCreated, this, &TimelineListModel::onCallCreated);
connect(CoreManager::getInstance()->getSettingsModel(), &SettingsModel::hideEmptyChatRoomsChanged, this, &TimelineListModel::update);
connect(CoreManager::getInstance()->getAccountSettingsModel(), &AccountSettingsModel::defaultRegistrationChanged, this, &TimelineListModel::update);
for(auto item : model->mList) {
auto newItem = qobject_cast<TimelineModel*>(item)->clone();
connect(newItem.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool)));
connect(newItem->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel);
mList << newItem;
}
}
TimelineListModel::~TimelineListModel(){
}
TimelineListModel* TimelineListModel::clone() const{
return new TimelineListModel(this);
}
// -----------------------------------------------------------------------------
void TimelineListModel::reset(){
@ -109,7 +133,7 @@ QSharedPointer<TimelineModel> TimelineListModel::getTimeline(std::shared_ptr<lin
}
}
if(create){
QSharedPointer<TimelineModel> model = TimelineModel::create(chatRoom);
QSharedPointer<TimelineModel> model = TimelineModel::create(this, chatRoom);
if(model){
connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool)));
connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel);
@ -157,7 +181,7 @@ QSharedPointer<ChatRoomModel> TimelineListModel::getChatRoomModel(std::shared_pt
return model;
}
if(create){
QSharedPointer<TimelineModel> model = TimelineModel::create(chatRoom);
QSharedPointer<TimelineModel> model = TimelineModel::create(this, chatRoom);
if(model){
connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool)));
connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel);
@ -198,8 +222,13 @@ void TimelineListModel::onSelectedHasChanged(bool selected){
}else
setSelectedCount(mSelectedCount+1);
emit selectedChanged(qobject_cast<TimelineModel*>(sender()));
} else
} else {
if( this == CoreManager::getInstance()->getTimelineListModel()) {// Clean memory only if the selection is about the main list.
auto timeline = qobject_cast<TimelineModel*>(sender());
timeline->getChatRoomModel()->resetData();// Cleanup leaving chat room
}
setSelectedCount(mSelectedCount-1);
}
}
void TimelineListModel::updateTimelines () {
@ -252,7 +281,7 @@ void TimelineListModel::updateTimelines () {
auto haveTimeline = getTimeline(dbChatRoom, false);
if(!haveTimeline && dbChatRoom){// Create a new Timeline if needed
QSharedPointer<TimelineModel> model = TimelineModel::create(dbChatRoom, callLogs);
QSharedPointer<TimelineModel> model = TimelineModel::create(this, dbChatRoom, callLogs);
if( model){
connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool)));
connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel);
@ -302,7 +331,7 @@ void TimelineListModel::select(ChatRoomModel * chatRoomModel){
void TimelineListModel::onChatRoomStateChanged(const std::shared_ptr<linphone::ChatRoom> &chatRoom,linphone::ChatRoom::State state){
if( state == linphone::ChatRoom::State::Created
&& !getTimeline(chatRoom, false)){// Create a new Timeline if needed
QSharedPointer<TimelineModel> model = TimelineModel::create(chatRoom);
QSharedPointer<TimelineModel> model = TimelineModel::create(this, chatRoom);
if(model){
connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool)));
connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel);

View file

@ -39,7 +39,9 @@ public:
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
TimelineListModel (QObject *parent = Q_NULLPTR);
TimelineListModel(const TimelineListModel* model);
virtual ~TimelineListModel();
TimelineListModel * clone() const;
void reset();
void selectAll(const bool& selected);
TimelineModel * getAt(const int& index);

View file

@ -66,8 +66,8 @@ void TimelineModel::connectTo(ChatRoomListener * listener){
}
// =============================================================================
QSharedPointer<TimelineModel> TimelineModel::create(std::shared_ptr<linphone::ChatRoom> chatRoom, const std::list<std::shared_ptr<linphone::CallLog>>& callLogs, QObject *parent){
if((!chatRoom || chatRoom->getState() != linphone::ChatRoom::State::Deleted) && (!CoreManager::getInstance()->getTimelineListModel() || !CoreManager::getInstance()->getTimelineListModel()->getTimeline(chatRoom, false)) ) {
QSharedPointer<TimelineModel> TimelineModel::create(TimelineListModel * mainList, std::shared_ptr<linphone::ChatRoom> chatRoom, const std::list<std::shared_ptr<linphone::CallLog>>& callLogs, QObject *parent){
if((!chatRoom || chatRoom->getState() != linphone::ChatRoom::State::Deleted) && (!mainList || !mainList->getTimeline(chatRoom, false)) ) {
QSharedPointer<TimelineModel> model = QSharedPointer<TimelineModel>::create(chatRoom, parent);
if(model && model->getChatRoomModel()){
@ -122,6 +122,25 @@ TimelineModel::TimelineModel (std::shared_ptr<linphone::ChatRoom> chatRoom, QObj
mSelected = false;
}
TimelineModel::TimelineModel(const TimelineModel * model){
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
mChatRoomModel = model->mChatRoomModel;
if( mChatRoomModel ){
QObject::connect(this, &TimelineModel::selectedChanged, this, &TimelineModel::updateUnreadCount);
QObject::connect(CoreManager::getInstance()->getAccountSettingsModel(), &AccountSettingsModel::defaultAccountChanged, this, &TimelineModel::onDefaultAccountChanged);
}
if(mChatRoomModel->getChatRoom()){
mChatRoomListener = model->mChatRoomListener;
connectTo(mChatRoomListener.get());
mChatRoomModel->getChatRoom()->addListener(mChatRoomListener);
}
mSelected = model->mSelected;
}
QSharedPointer<TimelineModel> TimelineModel::clone()const{
return QSharedPointer<TimelineModel>::create(this);
}
TimelineModel::~TimelineModel(){
if( mChatRoomModel->getChatRoom())
mChatRoomModel->getChatRoom()->removeListener(mChatRoomListener);
@ -152,7 +171,7 @@ ChatRoomModel *TimelineModel::getChatRoomModel() const{
}
void TimelineModel::setSelected(const bool& selected){
if(mChatRoomModel && selected != mSelected){
if(mChatRoomModel && (selected != mSelected || selected)){
mSelected = selected;
if(mSelected){
qInfo() << "Chat room selected : Subject :" << mChatRoomModel->getSubject()
@ -166,10 +185,7 @@ void TimelineModel::setSelected(const bool& selected){
<< ", canHandleParticipants:"<< mChatRoomModel->canHandleParticipants()
<< ", isReadOnly:" << mChatRoomModel->isReadOnly()
<< ", state:" << mChatRoomModel->getState();
QQmlEngine *engine = App::getInstance()->getEngine();
engine->clearComponentCache();
}else
mChatRoomModel->resetData();// Cleanup leaving chat room
}
emit selectedChanged(mSelected);
}
}

View file

@ -33,15 +33,19 @@
class ChatRoomModel;
class ChatRoomListener;
class TimelineListModel;
class TimelineModel : public QObject {
Q_OBJECT
public:
static QSharedPointer<TimelineModel> create(std::shared_ptr<linphone::ChatRoom> chatRoom, const std::list<std::shared_ptr<linphone::CallLog>>& callLogs = std::list<std::shared_ptr<linphone::CallLog>>(), QObject *parent = Q_NULLPTR);
static QSharedPointer<TimelineModel> create(TimelineListModel * mainList, std::shared_ptr<linphone::ChatRoom> chatRoom, const std::list<std::shared_ptr<linphone::CallLog>>& callLogs = std::list<std::shared_ptr<linphone::CallLog>>(), QObject *parent = Q_NULLPTR);
TimelineModel (std::shared_ptr<linphone::ChatRoom> chatRoom, QObject *parent = Q_NULLPTR);
TimelineModel(const TimelineModel * model);
virtual ~TimelineModel();
QSharedPointer<TimelineModel> clone() const;
Q_PROPERTY(QString fullPeerAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged)
Q_PROPERTY(QString fullLocalAddress READ getFullLocalAddress NOTIFY fullLocalAddressChanged)
Q_PROPERTY(ChatRoomModel* chatRoomModel READ getChatRoomModel CONSTANT)

View file

@ -37,32 +37,14 @@
// -----------------------------------------------------------------------------
TimelineProxyModel::TimelineProxyModel (QObject *parent) : QSortFilterProxyModel(parent) {
CoreManager *coreManager = CoreManager::getInstance();
AccountSettingsModel *accountSettingsModel = coreManager->getAccountSettingsModel();
TimelineListModel * model = CoreManager::getInstance()->getTimelineListModel();
connect(model, SIGNAL(selectedCountChanged(int)), this, SIGNAL(selectedCountChanged(int)));
connect(model, &TimelineListModel::updated, this, &TimelineProxyModel::invalidate);
connect(model, &TimelineListModel::selectedChanged, this, &TimelineProxyModel::selectedChanged);
connect(model, &TimelineListModel::countChanged, this, &TimelineProxyModel::countChanged);
QObject::connect(accountSettingsModel, &AccountSettingsModel::defaultAccountChanged, this, [this]() {
qobject_cast<TimelineListModel*>(sourceModel())->update();
invalidate();
});
QObject::connect(coreManager->getSipAddressesModel(), &SipAddressesModel::sipAddressReset, this, [this]() {
qobject_cast<TimelineListModel*>(sourceModel())->reset();
invalidate();// Invalidate and reload GUI if the model has been reset
});
setSourceModel(model);
sort(0);
}
// -----------------------------------------------------------------------------
void TimelineProxyModel::unselectAll(){
qobject_cast<TimelineListModel*>(sourceModel())->selectAll(false);
if( sourceModel())
qobject_cast<TimelineListModel*>(sourceModel())->selectAll(false);
}
void TimelineProxyModel::setFilterFlags(const int& filterFlags){
@ -79,9 +61,49 @@ void TimelineProxyModel::setFilterText(const QString& text){
emit filterTextChanged();
}
}
TimelineProxyModel::TimelineListSource TimelineProxyModel::getListSource() const{
return mListSource;
}
void TimelineProxyModel::setListSource(const TimelineListSource& source){
if(source != mListSource) {
TimelineListModel * model = nullptr;
if( source != Undefined){
CoreManager *coreManager = CoreManager::getInstance();
AccountSettingsModel *accountSettingsModel = coreManager->getAccountSettingsModel();
model = source == Main ? CoreManager::getInstance()->getTimelineListModel() : CoreManager::getInstance()->getTimelineListModel()->clone();
connect(model, SIGNAL(selectedCountChanged(int)), this, SIGNAL(selectedCountChanged(int)));
connect(model, &TimelineListModel::updated, this, &TimelineProxyModel::invalidate);
connect(model, &TimelineListModel::selectedChanged, this, &TimelineProxyModel::selectedChanged);
connect(model, &TimelineListModel::countChanged, this, &TimelineProxyModel::countChanged);
QObject::connect(accountSettingsModel, &AccountSettingsModel::defaultAccountChanged, this, [this]() {
qobject_cast<TimelineListModel*>(sourceModel())->update();
invalidate();
});
QObject::connect(coreManager->getSipAddressesModel(), &SipAddressesModel::sipAddressReset, this, [this]() {
qobject_cast<TimelineListModel*>(sourceModel())->reset();
invalidate();// Invalidate and reload GUI if the model has been reset
});
}
if( mListSource != Main && sourceModel()){
sourceModel()->deleteLater();
}
setSourceModel(model);
sort(0);
mListSource = source;
emit listSourceChanged();
}
}
// -----------------------------------------------------------------------------
bool TimelineProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const {
if(!sourceModel())
return false;
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
auto timeline = sourceModel()->data(index).value<TimelineModel*>();
if(!timeline || !timeline->getChatRoomModel() || timeline->getChatRoomModel()->getState() == (int)linphone::ChatRoom::State::Deleted)
@ -116,6 +138,8 @@ bool TimelineProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex &sou
}
bool TimelineProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
if( !sourceModel())
return false;
const TimelineModel* a = sourceModel()->data(left).value<TimelineModel*>();
const TimelineModel* b = sourceModel()->data(right).value<TimelineModel*>();
bool aHaveUnread = a->getChatRoomModel()->getAllUnreadCount() > 0;

View file

@ -45,8 +45,17 @@ public:
};
Q_ENUM(TimelineFilter)
enum TimelineListSource{
Undefined,
Main, // Timeline list comes from the singleton stored in CoreManager.
Copy // Timeline list is created from Main but have no attach to the main list (aside of root items).
};
Q_ENUM(TimelineListSource)
TimelineProxyModel (QObject *parent = Q_NULLPTR);
Q_PROPERTY(TimelineListSource listSource READ getListSource WRITE setListSource NOTIFY listSourceChanged)
Q_PROPERTY(int filterFlags MEMBER mFilterFlags WRITE setFilterFlags NOTIFY filterFlagsChanged)
Q_PROPERTY(QString filterText MEMBER mFilterText WRITE setFilterText NOTIFY filterTextChanged)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
@ -55,12 +64,16 @@ public:
Q_INVOKABLE void setFilterFlags(const int& filterFlags);
Q_INVOKABLE void setFilterText(const QString& text);
TimelineListSource getListSource() const;
void setListSource(const TimelineListSource& source);
signals:
void countChanged();
void selectedCountChanged(int selectedCount);
void selectedChanged(TimelineModel * timelineModel);
void filterFlagsChanged();
void filterTextChanged();
void listSourceChanged();
protected:
@ -74,6 +87,7 @@ protected:
private:
int mFilterFlags = 0;
QString mFilterText;
TimelineListSource mListSource = Undefined;
};
#endif // TIMELINE_PROXY_MODEL_H_

View file

@ -90,11 +90,12 @@ DialogPlus {
Timeline {
id: timeline
showHistoryButton: false
updateSelectionModels: false
anchors.fill: parent
model: TimelineProxyModel{}
onEntryClicked:{
if( entry ) {
model: TimelineProxyModel{
listSource: TimelineProxyModel.Copy
}
onEntrySelected:{
if( entry) {
mainItem.chatRoomSelectedCallback(entry.chatRoomModel)
exit(1)
}

View file

@ -23,14 +23,12 @@ Rectangle {
property alias model: view.model
property string _selectedSipAddress
property bool showHistoryButton : CoreManager.callLogsCount
property bool updateSelectionModels : true
property bool isFilterVisible: searchView.visible || showFilterView
property bool showFiltersButtons: view.count > 0 || timeline.isFilterVisible || timeline.model.filterFlags > 0
// ---------------------------------------------------------------------------
signal entrySelected (TimelineModel entry)
signal entryClicked(TimelineModel entry)
signal showHistoryRequest()
// ---------------------------------------------------------------------------
@ -52,7 +50,7 @@ Rectangle {
timeline.entrySelected('')
}
}
onSelectedChanged : if(timelineModel && timeline.updateSelectionModels) timeline.entrySelected(timelineModel)
onSelectedChanged : if(timelineModel) timeline.entrySelected(timelineModel)
}
// -------------------------------------------------------------------------
// Legend.
@ -358,7 +356,6 @@ Rectangle {
ScrollableListView {
id: view
property alias updateSelectionModels: timeline.updateSelectionModels
Layout.fillHeight: true
Layout.fillWidth: true
currentIndex: -1
@ -370,8 +367,7 @@ Rectangle {
Connections{
target: $modelData
onSelectedChanged:{
gc()
if(view.updateSelectionModels && selected) {
if(selected) {
view.currentIndex = index;
}
}

View file

@ -90,10 +90,7 @@ Item {
preventStealing: false
onClicked: {
if(mouse.button == Qt.LeftButton){
timeline.entryClicked(mainItem.timelineModel)
if(view.updateSelectionModels)
mainItem.timelineModel.selected = true
view.currentIndex = mainItem.modelIndex;
mainItem.timelineModel.selected = true
}else{
mainItem.optionsToggled = !mainItem.optionsToggled
}

View file

@ -357,7 +357,9 @@ ApplicationWindow {
Layout.fillHeight: true
Layout.fillWidth: true
model: TimelineProxyModel{}
model: TimelineProxyModel{
listSource: TimelineProxyModel.Main
}
onEntrySelected:{
if( entry ) {