コード例 #1
0
PSZUC
TAccountXmpp::Contact_RosterUpdateItem(const CXmlNode * pXmlNodeItemRoster)
	{
	Assert(pXmlNodeItemRoster != NULL);
	Assert(pXmlNodeItemRoster->FCompareTagName("item"));
	PSZUC pszJid = pXmlNodeItemRoster->PszFindAttributeValueJid_NZ();
	PSZUC pszSubscription = pXmlNodeItemRoster->PszuFindAttributeValue_NZ("subscription");	/* // The attribute "subscription" is not present for a "<iq type='set'>" */
	if (!FCompareStrings(pszSubscription, "remove"))
		{
		#ifdef DEBUG_DELETE_TEMP_ACCOUNTS
		if (PszrCompareStringBeginNoCase(pszJid, "temp") != NULL)
			{
			MessageLog_AppendTextFormatSev(eSeverityErrorAssert, "#$I Removing contact $s from roster...\n", ++g_cContactsRemoved, pszJid);
			if (m_paSocket != NULL)
				m_paSocket->Socket_WriteXmlFormatted("<iq type='set'><query xmlns='jabber:iq:roster'><item jid='^s' subscription='remove'></item></query></iq>", pszJid);
			return NULL;
			}
		#endif
		TContact * pContact = Contact_PFindByJID(pszJid, eFindContact_kfCreateNew);
		Endorse(pContact == NULL);	// The attribute "jid" may not be valid (for example, missing the '@' character)
		if (pContact != NULL)
			pContact->XmppRosterSubscriptionUpdate(pszSubscription);
		}
	return pszJid;
	}
コード例 #2
0
TContact *
TAccountXmpp::TreeItemAccount_PContactAllocateNewToNavigationTree_NZ(PSZUC pszContactJID, PSZUC pszContactNameDisplay)
	{
	Assert(pszContactJID != NULL);
	Endorse(pszContactNameDisplay == NULL);	// Automatically generate a display name
	Assert(Contact_PFindByJID(pszContactJID, eFindContact_zDefault) == NULL && "The JID is already present!");
	Assert(!m_strJID.FCompareStringsJIDs(pszContactJID) && "The contact JID is the same as the JID of the XMPP account!");
	Assert(m_paTreeItemW_YZ != NULL && "No Tree Item to attach to");	// This line of code has to be revised
	TContact * pContact = new TContact(this);
	pContact->m_strRessource = pContact->m_strJidBare.AppendTextUntilCharacterPszr(pszContactJID, '/');
	pContact->m_strNameDisplayTyped = pszContactNameDisplay;
	pContact->TreeItemContact_GenerateDisplayNameFromJid();
	m_arraypaContacts.Add(PA_CHILD pContact);
	#ifdef WANT_TREE_NODE_NEW_CONTACT
	pContact->TreeItemW_DisplayWithinNavigationTreeBefore(m_pTreeItemContactNew);
	#else
	pContact->TreeItemW_DisplayWithinNavigationTree(this);
	#endif
	pContact->TreeItemContact_UpdateIcon();
	pContact->TreeItemW_EnsureVisible();	// Make sure the new contact is visible in the Navigation Tree
	pContact->m_tsCreated = Timestamp_GetCurrentDateTime();
	if (m_paSocket != NULL)
		Contact_RosterSubscribe(pContact);
	Configuration_Save();		// Save the configuration after adding a new contact (just in case the application crashes)
	TWallet::S_ContactAdded(pContact);	// Notify the wallet(s) there is a new contact
	return pContact;
	}
コード例 #3
0
void
CVaultEvents::WriteEventsToDiskIfModified()
	{
	Endorse(m_pEventLastSaved == NULL);	// Always write the vault to disk
	IEvent * pEventLastSaved = PGetEventLast_YZ();
	if (pEventLastSaved != m_pEventLastSaved)
		{
		int cEvents = m_arraypaEvents.GetSize();
		CBinXospStanzaForDisk binXmlEvents;
		binXmlEvents.PbbAllocateMemoryAndEmpty_YZ(100 + 64 * cEvents);	// Pre-allocate about 64 bytes per event.  This estimate will reduce the number of memory re-allocations.
		binXmlEvents.BinAppendText_VE("<E v='1' c='$i'>\n", cEvents);
		m_arraypaEvents.EventsSerializeForDisk(INOUT &binXmlEvents);
		binXmlEvents.BinAppendText_VE("</E>");
		TRACE3("CVaultEvents::WriteEventsToDiskIfModified($Q) for $s ^C", &m_sPathFileName, m_pParent->TreeItem_PszGetNameDisplay(), m_pParent);
		if (binXmlEvents.BinFileWriteE(m_sPathFileName) == errSuccess)
			m_pEventLastSaved = pEventLastSaved;
		}
	else
		{
		//TRACE2("CVaultEvents::WriteEventsToDiskIfModified(^s) m_pEventLastSaved == pEventLastSaved = 0x$p", m_pParent->TreeItem_PszGetNameDisplay(), pEventLastSaved);
		if (pEventLastSaved == NULL)
			{
			Assert(m_arraypaEvents.FIsEmpty());
			}
		}
	}
コード例 #4
0
void
CBinXcpStanza::BinXmlAppendAttributeOfContactIdentifierOfGroupSenderForEvent(const IEvent * pEvent)
	{
	AssertValidEvent(pEvent);
	Endorse(m_pContact == NULL);	// Saving to disk
	/*
	TContact * pContact = pEvent->m_pContactGroupSender_YZ;
	if (pContact != NULL && pContact != m_pContact)
		{
		Assert(pContact->EGetRuntimeClass() == RTI(TContact));
		pContact->BinAppendXmlAttributeOfContactIdentifier(INOUT this, d_chXCPa_pContactGroupSender);
		}
	*/
	if (pEvent->m_pContactGroupSender_YZ != m_pContact)
		{
		Endorse(pEvent->m_pContactGroupSender_YZ == NULL);	// The event was sent, or was received on a 1-to-1 conversation.  If this pointer is NULL, then the method BinAppendXmlAttributeOfContactIdentifier() will ignore it
		BinAppendXmlAttributeOfContactIdentifier(d_chXCPa_pContactGroupSender, pEvent->m_pContactGroupSender_YZ);
		}
	}
コード例 #5
0
//	Display the stanza message to the GUI
void
TContact::ChatLogContact_DisplayStanzaToUI(const CXmlNode * pXmlNodeMessageStanza)
	{
	Assert(pXmlNodeMessageStanza != NULL);
	Assert(m_pAccount != NULL);
	Endorse(m_paTreeItemW_YZ == NULL);
	m_tsmLastStanzaReceived = g_tsmMinutesSinceApplicationStarted;

	#if 0
	if (m_uFlagsContact & (FC_kfContactNeedsInvitation | FC_kfCommentContainsInvitation | FC_kfContactUnsolicited))
		{
		// If we receive a stanza from a contact, it means there is no need to to send an invitation because the stanza response is the proof the contact was established
		if (m_uFlagsContact & FC_kfContactNeedsInvitation)
			{
			m_uFlagsContact &= ~FC_kfContactNeedsInvitation;
			NoticeListAuxiliary_DeleteAllNoticesRelatedToTreeItem(this);
			}
		if (m_uFlagsContact & (FC_kfCommentContainsInvitation | FC_kfContactUnsolicited))
			{
			// Compare if the message is the handshake of the invitation
			PSZUC pszMessageBody = pXmlNodeMessageStanza->PszuFindElementValue_NZ(c_sza_body);	// The body may contain the invitation message
			if (m_uFlagsContact & FC_kfCommentContainsInvitation)
				{
				if (m_strComment.FCompareStringsExactCase(pszMessageBody))
					{
					m_strComment.Empty();	// Remove the invitation
					m_uFlagsContact &= ~FC_kfCommentContainsInvitation;
					TreeItemContact_UpdateIcon();
					return;	// Don't display the message because it is the invitation encoded in Base64
					}
				}
			if (m_uFlagsContact & FC_kfContactUnsolicited)
				{
				// We have an 'unsolicited' contact, however this may be to an incomplete handshake
				/*
				TContact * pContactInvitation = m_pAccount->PFindContactByComment(pszMessageBody);
				if (pContactInvitation != NULL && pContactInvitation != this)
					{
					m_uFlagsContact &= ~(FC_kfContactUnsolicited | FC_kfCommentContainsInvitation);
					BOOL fSelectContact = (NavigationTree_PGetSelectedTreeItem() == pContactInvitation);
					m_pAccount->Contact_DeleteSafely(PA_DELETING pContactInvitation);
					TreeItemContact_UpdateIcon();
					if (fSelectContact)
						TreeItem_SelectWithinNavigationTree();
					return;
					}
				*/
				} // if
			}
		} // if
	#endif

	PSZUC pszuMessageBody = ChatLog_PwGetLayout_NZ()->ChatLog_DisplayStanzaToUser(pXmlNodeMessageStanza);
	TreeItemChatLog_IconUpdateOnNewMessageArrivedFromContact(pszuMessageBody, this);
	} // ChatLogContact_DisplayStanzaToUI()
コード例 #6
0
void
TAccountXmpp::Contact_PresenceUpdate(const CXmlNode * pXmlNodeStanzaPresence)
	{
	Assert(pXmlNodeStanzaPresence != NULL);
	Assert(pXmlNodeStanzaPresence->FCompareTagName("presence"));
	// Find the contact to update the presence
	TContact * pContact = Contact_PFindByJID(pXmlNodeStanzaPresence->PszFindAttributeValueFrom_NZ(), eFindContact_kfCreateNew);
	Endorse(pContact == NULL);	// The <presence> stanza sometimes is just a reply from the server, which means the attribute "from" is not a contact.
	if (pContact != NULL)
		pContact->XmppPresenceUpdateIcon(pXmlNodeStanzaPresence);
	}
コード例 #7
0
//	Generic method to set the error
void
CErrorMessage::SetErrorCode(EError err)
	{
	Endorse(err == errSuccess);	// This should do no harm
	if (err != m_err)
		{
		// Cascade the error description from the previous error (if any) to the error detail.  This is useful for chaining errors.
		m_strErrorDetails.AppendSeparatorCommaAndTextU(PszuGetErrorDescriptionReFormatted());
		m_strErrorDescription.Empty();
		m_err = err;
		m_eSeverity = eSeverityErrorWarning;
		}
	} // SetErrorCode()
コード例 #8
0
void
TProfile::TreeItemProfile_DisplayProfileInfoWithinNavigationTree()
	{
	Endorse(g_pTreeItemProfiles == NULL); // Display the profile at the root rather than under "Profiles"
	TreeItemW_DisplayWithinNavigationTree(g_pTreeItemProfiles, eMenuIconIdentities);
	TAccountXmpp * pAccount = NULL;
	TAccountXmpp ** ppAccountStop;
	TAccountXmpp ** ppAccount = m_arraypaAccountsXmpp.PrgpGetAccountsStop(OUT &ppAccountStop);
	while (ppAccount != ppAccountStop)
		{
		pAccount = *ppAccount++;
		pAccount->PGetAlias_NZ()->TreeItemW_DisplayWithinNavigationTreeExpand(this, pAccount->m_strJID, eMenuIconXmpp);
		}
	}
コード例 #9
0
//	Must call Socket_DisconnectUI() when the UI is no longer needed
void
TAccountXmpp::Socket_ConnectUI(ISocketUI * piSocketUI, BOOL fCreateAccount)
	{
	Endorse(piSocketUI == NULL);
	PSZUC pszMessageConnecting = TreeItemAccount_SetIconConnectingToServer_Gsb();
	if (piSocketUI == NULL)
		StatusBar_SetTextU(pszMessageConnecting);
	else
		piSocketUI->SocketUI_DisplayMessage(eSeverityInfoTextBlack, pszMessageConnecting);	// Display the message to the user before creating the socket (the creation of the socket is CPU intensive, and the 0.5 second delay will be noticeable by the user)
	if (m_paSocket == NULL)
		m_paSocket = new CSocketXmpp(this);
	m_paSocket->SocketUI_Init(piSocketUI, fCreateAccount);
	m_paSocket->Socket_Connect();
	}
コード例 #10
0
void
TAccountXmpp::TreeItemAccount_DisplayWithinNavigationTree()
	{
	Endorse(g_pTreeItemInbox == NULL);	// Display the account at the root of the Navigation Tree
	TreeItemW_DisplayWithinNavigationTree(g_pTreeItemInbox);
	TContact ** ppContactStop;
	TContact ** ppContact = m_arraypaContacts.PrgpGetContactsStop(OUT &ppContactStop);
	while (ppContact != ppContactStop)
		{
		TContact * pContact = *ppContact++;
		Assert(pContact != NULL);
		Assert(pContact->EGetRuntimeClass() == RTI(TContact));
		Assert(pContact->m_pAccount == this);
		pContact->TreeItemContact_DisplayWithinNavigationTree();
		} // while
	#ifdef WANT_TREE_NODE_NEW_CONTACT
	m_pTreeItemContactNew = new TContactNew(this);
	#endif

	TGroup ** ppGroupStop;
	TGroup ** ppGroup = m_arraypaGroups.PrgpGetGroupsStop(OUT &ppGroupStop);
	while (ppGroup != ppGroupStop)
		{
		TGroup * pGroup = *ppGroup++;
		Assert(pGroup != NULL);
		Assert(pGroup->EGetRuntimeClass() == RTI(TGroup));
		Assert(pGroup->m_pAccount == this);
		if (pGroup->TreeItemGroup_FCanDisplayWithinNavigationTree())
			pGroup->TreeItemGroup_DisplayWithinNavigationTree();
		}
	TreeItemW_ExpandAccordingToSavedState();
	TreeItemAccount_UpdateIcon();

	#if FIX_THIS
	// Display the alias as well
	m_paAlias->TreeItem_DisplayWithinNavigationTree(m_pProfileParent, m_strJID, eMenuIconXmpp);
	#endif
	} // TreeItemAccount_DisplayWithinNavigationTree()
コード例 #11
0
//	Core routine to populate the Navigation Tree.
//
//	IMPLEMENTATION NOTES
//	At the moment the routine is written for a single configuration.  This code will have to be revised to support multiple configurations.
void
NavigationTree_PopulateTreeItemsAccordingToSelectedProfile(TProfile * pProfileSelected, BOOL fCreateNewProfile)
	{
	Endorse(pProfileSelected == NULL);	// Display all profiles
	Assert(g_pwNavigationTree != NULL);
	g_oConfiguration.m_pProfileSelected = pProfileSelected;

	// Flush the existing content of the Navigation Tree
	g_pwNavigationTree->NavigationTree_TreeItemUnselect();
	WTreeWidget * pwTreeView = g_pwNavigationTree->m_pwTreeView;
	QTreeWidgetItemIterator oIterator(pwTreeView);
	while (TRUE)
		{
		CTreeItemW * pTreeWidgetItem = (CTreeItemW *)*oIterator++;
		if (pTreeWidgetItem == NULL)
			break;
		ITreeItem * piTreeItem = pTreeWidgetItem->m_piTreeItem;
		Assert(piTreeItem != NULL);
		Assert(PGetRuntimeInterfaceOf_ITreeItem(piTreeItem) == piTreeItem);	// Make sure the pointer is valid
		Assert(piTreeItem->m_paTreeItemW_YZ != NULL);
		// Remember the state of each Tree Item before switching profile, so next time the profile is re-selected, then the entire arborescence is restored.
		if (piTreeItem->m_paTreeItemW_YZ->isExpanded())
			piTreeItem->m_uFlagsTreeItem |= ITreeItem::FTI_kfTreeItem_IsExpanded;
		else
			piTreeItem->m_uFlagsTreeItem &= ~ITreeItem::FTI_kfTreeItem_IsExpanded;

		//MessageLog_AppendTextFormatSev(eSeverityWarningToErrorLog, "0x$p: $s  m_uFlagsTreeItem = 0x$x\n", piTreeItem, piTreeItem->TreeItem_PszGetNameDisplay(), piTreeItem->m_uFlagsTreeItem);
		piTreeItem->m_paTreeItemW_YZ = NULL;	// This would cause a memory leak in the absence of pwTreeView->clear()
		} // while
	pwTreeView->clear();
	delete g_pTreeItemInbox;
	g_pTreeItemInbox = NULL;
	delete g_pTreeItemProfiles;
	g_pTreeItemProfiles = NULL;

	int cProfiles;
	TProfile ** prgpProfiles = g_oConfiguration.m_arraypaProfiles.PrgpGetProfiles(OUT &cProfiles);
	if (cProfiles == 1 && !fCreateNewProfile)
		pProfileSelected = prgpProfiles[0];	// If there is only one profile, then pretend the user selected that profile.  There is no need to show extra nodes which may confuse the user
	if (pProfileSelected != NULL)
		{
		// Display a single profile.  This is done by displaying the contacts first, and then the profile at the bottom
		pProfileSelected->TreeItemProfile_DisplayContactsWithinNavigationTree();
		if (pProfileSelected->m_arraypaAccountsXmpp.FIsEmpty())
			pProfileSelected->TreeItemProfile_DisplayProfileInfoWithinNavigationTree();
		pProfileSelected->TreeItemProfile_DisplayApplicationsWithinNavigationTree();
		}
	else
		{
		// Since we are displaying multiple profiles, we need to create two root items for the 'Inbox' and the 'Profiles'
		g_pTreeItemInbox = new TTreeItemInbox;
		g_pTreeItemProfiles = new TProfiles;

		for (int iProfile = 0; iProfile < cProfiles; iProfile++)
			{
			TProfile * pProfile = prgpProfiles[iProfile];
			Assert(pProfile->EGetRuntimeClass() == RTI(TProfile));
			pProfile->TreeItemProfile_DisplayContactsWithinNavigationTree();
			pProfile->TreeItemProfile_DisplayProfileInfoWithinNavigationTree();
			pProfile->TreeItemProfile_DisplayApplicationsWithinNavigationTree();
			}
		}
	NavigationTree_UpdateNameOfSelectedProfile();
	if (fCreateNewProfile || cProfiles == 0)
		{
		// Set the focus to "Profiles" if creating a new profile, or if there is no profile
		Assert(g_pTreeItemProfiles != NULL);
		g_pTreeItemProfiles->TreeItemLayout_SetFocus();
		}
	else
		pwTreeView->setCurrentItem(pwTreeView->topLevelItem(0));	// Select the first item
	} // NavigationTree_PopulateTreeItemsAccordingToSelectedProfile()
コード例 #12
0
//	Find the contact matching the JID and update the contact resource (if any).
//	If the JID is not in the contact list, the method will create a new contact to the list according to the value of eFindContact.
//	This method may return NULL if the JID is empty/invalid because a new contact cannot not be created.
//
//	IMPLEMENTATION NOTES
//	This method should use a hash table to quickly find a contact from its JID and/or use a pointer to cache the last contact found.
TContact *
TAccountXmpp::Contact_PFindByJID(PSZUC pszContactJID, EFindContact eFindContact)
	{
	Assert(pszContactJID != NULL);
	if (pszContactJID != NULL)
		{
		// Find the resource
		PSZUC pszResource = pszContactJID;
		while (TRUE)
			{
			if (*pszResource == '/')
				break;
			if (*pszResource == '\0')
				break;
			pszResource++;
			} // while
		const int cchJID = pszResource - pszContactJID;
		if (cchJID > 0)
			{
			TContact * pContact;
			TContact ** ppContactStop;
			TContact ** ppContact = m_arraypaContacts.PrgpGetContactsStop(OUT &ppContactStop);
			while (ppContact != ppContactStop)
				{
				pContact = *ppContact++;
//				Assert(!m_strJID.FCompareStringsJIDs(pContact->m_strJidBare) && "Contact should not have the same JID as its parent XMPP account!");
				//MessageLog_AppendTextFormatCo(d_coBlack, "Comparing $s with $S\n", pszContactJID, &pContact->m_strJidBare);
				if (pContact->m_strJidBare.FCompareStringsNoCaseCch(pszContactJID, cchJID))
					{
					// We have found our contact
					if (pContact->TreeItemFlags_FuIsInvisible())
						{
						Endorse(pContact->m_paTreeItemW_YZ != NULL);	// Typically an invisible contact should not appear in the Navigation Tree, however there are situations where it is. For instance, if the contact was just deleted, or if displayed into the list of "Deleted Items"
						if (eFindContact & eFindContact_kfMakeVisible)
							pContact->TreeItemContact_DisplayWithinNavigationTreeAndClearInvisibleFlag();
						}
					if (*pszResource != '\0')
						{
						// Update the contact resource
						if (!pContact->m_strRessource.FCompareStringsExactCase(pszResource))
							{
							if (pContact->m_strRessource.FIsEmptyString())
								MessageLog_AppendTextFormatCo(d_coBlack, "Contact $S: Assigning resource '$s' (m_uFlagsTreeItem = 0x$x)\n", &pContact->m_strJidBare, pszResource, m_uFlagsTreeItem);
							else
								MessageLog_AppendTextFormatCo(COX_MakeBold(d_coBlack), "Contact $S: Updating resource from '$S' to '$s'\n", &pContact->m_strJidBare, &pContact->m_strRessource, pszResource);
							//pContact->Xcp_ServiceDiscovery();	// The resource changed, therefore query the remote client for its capabilities
							pContact->SetFlagXcpComposingSendTimestampsOfLastKnownEvents();	// Each time the resource change, re-send the timestamps of the last known events so we give an opportunity to synchronize
							//pContact->m_cVersionXCP = 0;	// Also, reset the XCP version, to make sure the device connects properly
							}
						pContact->m_strRessource.InitFromStringU(pszResource);
						}
					return pContact;
					}
				} // while
			// We could not find the contact, so create a new one if the JID meets the minimal conditions
			if ((eFindContact & eFindContact_kfCreateNew) &&
				PcheValidateJID(pszContactJID) == NULL &&			// Make sure the JID is somewhat valid to create a new contact
				!m_strJID.FCompareStringsJIDs(pszContactJID))		// Make sure the JID is not the same as the account.  It makes no sense to create a contact with the same JID as its parent account.  This situation occurs rarely when the server sends a stanza where the 'from' contains the JID of the account.
				{
				MessageLog_AppendTextFormatSev(eSeverityInfoTextBlack, "Contact_PFindByJID('$s') - Creating contact for account $S\n", pszContactJID, &m_strJID);
				pContact = TreeItemAccount_PContactAllocateNewToNavigationTree_NZ(IN pszContactJID);
				if (eFindContact & eFindContact_kfCreateAsUnsolicited)
					pContact->SetFlagContactAsUnsolicited();
				return pContact;
				}
			} // if
		} // if
	return NULL;
	} // Contact_PFindByJID()
コード例 #13
0
void
WChatLog::ChatLog_EventsDisplay(const CArrayPtrEvents & arraypEvents, int iEventStart)
	{
	Assert(iEventStart >= 0);
	Assert(m_oTextBlockComposing.isValid());
//	CSocketXmpp * pSocket = m_pContactOrGroup->Xmpp_PGetSocketOnlyIfReady();

	OCursorSelectBlock oCursor(m_oTextBlockComposing);
	QTextBlock oTextBlockEvent;	// Text block for each event to insert
	IEvent ** ppEventStop;
	IEvent ** ppEventFirst = arraypEvents.PrgpGetEventsStop(OUT &ppEventStop) + iEventStart;
	IEvent ** ppEvent = ppEventFirst;
	if (!m_fDisplayAllMessages)
		{
		// For performance, limit the display to the last 100 events
		if (ppEventStop != NULL)
			{
			IEvent ** ppEventStart = ppEventStop - 100;
			if (ppEventStart > ppEvent)
				{
				ppEvent = ppEventStart;

				QTextBlockFormat oFormat;
				oFormat.setAlignment(Qt::AlignHCenter);
				oFormat.setBackground(c_brushSilver);	// Use a silver color
				oCursor.setBlockFormat(oFormat);
				g_strScratchBufferStatusBar.Format("<a href='" d_SzMakeCambrianAction(d_szCambrianAction_DisplayAllHistory) "'>Display complete history ($I messages)</a>", arraypEvents.GetSize());
				oCursor.insertHtml(g_strScratchBufferStatusBar);
				oCursor.AppendBlockBlank();
				}
			}
		}

	while (ppEvent < ppEventStop)
		{
		IEvent * pEvent = *ppEvent++;
		AssertValidEvent(pEvent);
		if (pEvent->m_tsEventID >= m_tsMidnightNext)
			{
			QDateTime dtl = QDateTime::fromMSecsSinceEpoch(pEvent->m_tsEventID).toLocalTime();
			QDate date = dtl.date();	// Strip the time of the day
			m_tsMidnightNext = QDateTime(date).toMSecsSinceEpoch() + d_ts_cDays;	// I am sure there is a more elegant way to strip the time from a date, however at the moment I don't have time to investigate a better solution (and this code works)

			QTextBlockFormat oFormatBlock;
			oFormatBlock.setAlignment(Qt::AlignHCenter);
			oFormatBlock.setBackground(c_brushSilver);
			oCursor.setBlockFormat(oFormatBlock);
			QTextCharFormat oFormatChar; // = oCursor.charFormat();
			oFormatChar.setFontWeight(QFont::Bold);
			//oFormatChar.setFontItalic(true);
			//oCursor.setCharFormat(oFormatChar);
			oCursor.insertText(date.toString("dddd MMMM d, yyyy"), oFormatChar);
			oCursor.AppendBlockBlank();
			}
		oTextBlockEvent = oCursor.block();	// Get the current block under the cursor
		Endorse(oTextBlockEvent.userData() == NULL);

		if (pEvent->m_uFlagsEvent & IEvent::FE_kfReplacing)
			{
			MessageLog_AppendTextFormatSev(eSeverityComment, "Attempting to replace Event ID $t\n", pEvent->m_tsEventID);
			QTextBlock oTextBlockUpdate;
			QTextBlock oTextBlockTemp = document()->lastBlock();
			IEvent * pEventOld = pEvent;
			const EEventClass eEventClassUpdater = pEvent->Event_FIsEventTypeSent() ? CEventUpdaterSent::c_eEventClass : CEventUpdaterReceived::c_eEventClass;	// Which updater to search for
			// The event is replacing an older event.  This code is a bit complex because the Chat Log may not display all events and therefore we need to find the most recent block displaying the most recent event.
			IEvent ** ppEventStop;
			IEvent ** ppEventFirst = pEvent->m_pVaultParent_NZ->m_arraypaEvents.PrgpGetEventsStop(OUT &ppEventStop);
			IEvent ** ppEventCompare = ppEventStop;	// Search the array from the end, as the event to search is likely to be a recent one
			while (ppEventFirst < ppEventCompare)
				{
				// Find the updater which should be right before the replacing event
				IEvent * pEventTemp = *--ppEventCompare;
				TryAgain:
				if (pEventTemp == pEventOld && ppEventFirst < ppEventCompare)
					{
					CEventUpdaterSent * pEventUpdater = (CEventUpdaterSent *)*--ppEventCompare;	// Get the updater which is just before the event
					if (pEventUpdater->EGetEventClass() != eEventClassUpdater)
						{
						MessageLog_AppendTextFormatSev(eSeverityErrorWarning, "\t Missing Updater for Event ID $t ({tL}); instead found class '$U' with Event ID $t.\n", pEventTemp->m_tsEventID, pEventTemp->m_tsEventID, pEventUpdater->EGetEventClass(), pEventUpdater->m_tsEventID);
						pEvent->m_uFlagsEvent &= ~IEvent::FE_kfReplacing;	// Remove the bit to avoid displaying the error again and again
						pEvent->m_pVaultParent_NZ->SetModified();			// Save the change
						continue;
						}
					const TIMESTAMP tsEventIdOld = pEventUpdater->m_tsEventIdOld;
					MessageLog_AppendTextFormatSev(eSeverityNoise, "\t [$i] Found updater: $t  ->  $t\n", ppEventCompare - ppEventFirst, pEventUpdater->m_tsEventIdNew, tsEventIdOld);
					// Now, search for the block containing the replacement event
					while (oTextBlockTemp.isValid())
						{
						OTextBlockUserDataEvent * pUserData = (OTextBlockUserDataEvent *)oTextBlockTemp.userData();
						if (pUserData != NULL)
							{
							TIMESTAMP_DELTA dtsEvent = (pUserData->m_pEvent->m_tsEventID - tsEventIdOld);
							MessageLog_AppendTextFormatCo(d_coPurple, "Comparing block Event ID $t with $t: dtsEvent = $T\n", pUserData->m_pEvent->m_tsEventID, tsEventIdOld, dtsEvent);
							if (dtsEvent <= 0)
								{
								if (dtsEvent == 0)
									{
									MessageLog_AppendTextFormatSev(eSeverityNoise, "\t Found matching textblock for replacement: Event ID $t  ->  $t\n", pEventOld->m_tsEventID, tsEventIdOld);
									oTextBlockUpdate = oTextBlockTemp;
									}
								break;
								}
							}
						oTextBlockTemp = oTextBlockTemp.previous();
						} // while
					// Keep searching in case there are chained updated events
					while (ppEventFirst < ppEventCompare)
						{
						pEventTemp = *--ppEventCompare;
						if (pEventTemp->m_tsEventID == tsEventIdOld)
							{
							MessageLog_AppendTextFormatSev(eSeverityNoise, "\t [$i] Found chained replacement: Event ID $t  -> $t\n", ppEventCompare - ppEventFirst, pEvent->m_tsEventID, tsEventIdOld);
							pEventTemp->m_uFlagsEvent |= IEvent::FE_kfReplaced;
							pEventOld = pEventTemp;
							goto TryAgain;
							}
						} // while
					} // if
				} // while
			if (oTextBlockUpdate.isValid())
				{
				MessageLog_AppendTextFormatSev(eSeverityNoise, "\t Event ID $t is updating its old Event ID $t\n", pEvent->m_tsEventID, pEventOld->m_tsEventID);
				OCursorSelectBlock oCursorEventOld(oTextBlockUpdate);
				pEvent->ChatLogUpdateTextBlock(INOUT &oCursorEventOld);
				continue;
				}
			MessageLog_AppendTextFormatSev(eSeverityErrorWarning, "Event ID $t is replacing another event which cannot be found\n", pEvent->m_tsEventID);
			} // if (replacing)
		oTextBlockEvent.setUserData(PA_CHILD new OTextBlockUserDataEvent(pEvent));		// Assign an event for each text block
		pEvent->ChatLogUpdateTextBlock(INOUT &oCursor);
		if ((pEvent->m_uFlagsEvent & IEvent::FE_kfEventHidden) == 0)
			oCursor.AppendBlockBlank();	// If the event is visible, then add a new text block (otherwise it will reuse the same old block)
		else
			oTextBlockEvent.setUserData(NULL);	// Since we are reusing the same block, delete its userdata so we may assing another OTextBlockUserDataEvent
		} // while
	m_oTextBlockComposing = oCursor.block();
	ChatLog_ChatStateTextAppend(INOUT oCursor);
	Widget_ScrollToEnd(INOUT this);
	} // ChatLog_EventsDisplay()