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; }
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; }
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()); } } }
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); } }
// 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()
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); }
// 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()
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); } }
// 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(); }
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()
// 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()
// 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()
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()