FNCChat: Message list calculation appears to break when I have images shown next to a message.

Dear friends,

When I add messages it looks real nice, everything works.

As soon as I add a message with an image shown next to it, it's like the rendering of the messages goes to heck and it suddenly can't figure out where the bottom of the last message is.

This might be related to the other topic i started a week ago, where it's like the message list ends "under the bottom" of the client area.

I can almost make i work by disabling "Reload", but it still shows the last message something like 30 pixels under the bottom of the FNCChat message area, but it still goes crazy when new messages are added.

This ONLY happens after adding a message with an image as described in this topic.

Hi,

Just to confirm, did you try this with the latest version from last week?

I was not aware that a new version of UI Pack was released last week, so no, I did not.

Can you retry this with the latest changes? The following works as expected here:

procedure TForm1.Button1Click(Sender: TObject);
var
  ci: TTMSFNCChatItem;
begin
  TMSFNCChat1.BeginUpdate;
  ci := TMSFNCChat1.ChatMessages.Add;
  ci.BitmapName := '1.png'; //Assign bitmap from BitmapContainer
  ci.Text := 'this is a longer message so it will break up into multiple lines';
  if (Random(100) mod 2) > 0 then
  begin
    ci.MessageLocation := cmlRight;
    ci.Fill.Color := gcDodgerblue;
    ci.TextColor := gcWhite;
  end;
  TMSFNCChat1.EndUpdate;
  TMSFNCChat1.ScrollToBottom;
end;

I just did and i appears that it works as expected, thank you :slight_smile:

On a completely different note, how much html does the chat messages support ?

You can find what is supported by the HTML engine in this Mini HTML guide: TMS Software | Support

There's still something not quite right, when the number of messages gets to be like 20 or 30, the list breaks again, with or without images.

I did notice that the "buppel" around one if the messages was rendered slightly higher than the others, like it thinks there was an extra line, but still showing the text on only one line, I don't know it helps you or not.

Can you send the code that you use to add items?

Sure..

try
// ChannelChat.ChatMessages.BeginUpdate;
MessageXML := newXMLDocument;
MessageXML.Encoding := 'utf8';
MessageXML.LoadFromXML(ChannelData);
MessageXML.Active := True;

    try
      if ZIMChannelType = 'public' then
        SectionNode := MessageXML.ChildNodes.FindNode('pub_msg')
      else
      if ZIMChannelType = 'private' then
        SectionNode := MessageXML.ChildNodes.FindNode('priv_msg')
      else
      if ZIMChannelType = 'direct' then
        SectionNode := MessageXML.ChildNodes.FindNode('direct_msg');
    except
      SectionNode := nil;
    end;

    if SectionNode <> nil then
    begin
      ItemNode := SectionNode.ChildNodes['msg-timestamp'];
      MsgUnixTime := StrToInt64Def(ItemNode.Text,0);
      if MsgUnixTime = 0 then
      begin
        MsgUnixTime := DateTimeToUnix(NOW);
      end
      else
      begin
        MsgTimestamp := UnixToDateTime(MsgUnixTime);
        LastMessageTimestamp := MsgUnixTime;
        // Timestamps are in UTC, convert to local time
        MsgTimestamp := MsgTimestamp - (ONE_MINUTE * TZ.Bias) - (ONE_MINUTE * TZ.DaylightBias);
      end;

      if SectionNode.ChildNodes['content-type'].Text <> 'channel-info' then
      begin
        TheMessageID := SectionNode.ChildNodes['message-id'].Text;
        // Remember the first (oldest) messageid for requesting more messages and last (newest) messageids as the bookmark of when we left..
        if LastMessageID = '' then
          LastMessageID := TheMessageID
        else
        begin
          TmpMessageID := Copy(LastMessageID,1,Pos('@',LastMessageID)-1);
          LastMsgID := StrToInt64Def(TmpMessageID,-1);
          TmpMessageID := Copy(TheMessageID,1,Pos('@',TheMessageID)-1);
          ThisMsgID := StrToInt64Def(TmpMessageID,-1);
          if ThisMsgID > LastMsgID then LastMessageID := TheMessageID;
        end;

        if FirstMessageID = '' then
          FirstMessageID := TheMessageID
        else
        begin
          TmpMessageID := Copy(FirstMessageID,1,Pos('@',FirstMessageID)-1);
          FirstMsgID := StrToInt64Def(TmpMessageID,-1);
          TmpMessageID := Copy(TheMessageID,1,Pos('@',TheMessageID)-1);
          ThisMsgID := StrToInt64Def(TmpMessageID,-1);
          if ThisMsgID < FirstMsgID then
          begin
            FirstMessageID := TheMessageID;
          end;
        end;
      end;

      if LastMessageID = TheMessageID then
      begin
        if (not Self.Focused) and (not ChannelChat.Focused) and (not ChannelChat.Parent.Focused) and (not EditMessage.Focused) and (not BtnAttachment.Focused) and (not BtnSend.Focused) then Self.Icon := ImageHolder.Picture.Icon;
      end
      else
        Self.Icon := Application.Icon;

      MsgSenderRealname := MIMEDecodeString(SectionNode.ChildNodes['sender-real-name'].Text);
      MsgSenderUsername := MIMEDecodeString(SectionNode.ChildNodes['sender-username'].Text);

      // For messages with large attachements, the <miniature> may be present and the <att_filename> will be present..
      // The <att_filename> is the attachment-id needed for retrieving the file from the media archive
      ItemNode := SectionNode.ChildNodes['content'];
      MessageLine := diMime.MimeDecodeString(ItemNode.Text);
      Scroll := True;
      if SectionNode.ChildNodes['content-type'].Text = 'channel-info' then
      begin
        if SectionNode.ChildNodes['user-status'].Text = 'ENTER' then
        begin
          EnteringUser := MIMEDecodeString(SectionNode.ChildNodes['sender-username'].Text);
          if lowercase(EnteringUser) <> ChannelClient.SSHUser then
          begin
            NewChatItem := ChannelChat.ChatMessages.Add;
            NewChatItem.Timestamp := NOW;
            NewChatItem.Title := 'User joined';
            NewChatItem.Text := MsgSenderRealname + ' have joined the channel';

// NewChatItem.StatusPosition := cisBottomLeft;
NewChatItem.MessageLocation := cmlLeft;
NewChatItem.Fill.Color := gcWheat;
end;
end
else
if SectionNode.ChildNodes['user-status'].Text = 'LEAVE' then
begin
NewChatItem := ChannelChat.ChatMessages.Add;
NewChatItem.Timestamp := NOW;
NewChatItem.Title := 'User leave';
NewChatItem.Text := MsgSenderRealname + ' have left the channel';
// NewChatItem.StatusPosition := cisBottomLeft;
NewChatItem.MessageLocation := cmlLeft;
NewChatItem.Fill.Color := gcWheat;
end
else
if SectionNode.ChildNodes['user-status'].Text = 'INFOANSWER' then
begin

        end
        else
        if SectionNode.ChildNodes['user-status'].Text = 'INFOREQ' then
        begin

        end
        else
        if SectionNode.ChildNodes['no-prior-messages'].Text <> '' then
        begin
          ArchiveStartReached := True;
          ChannelChat.Header.Text := '--- no more messages in the archive ---';
        end
        else
        if SectionNode.ChildNodes['end-of-archive'].Text <> '' then
        begin
          ArchiveEndReached := True;
          ChannelChat.Footer.Visible := False;
        end;
      end
      else
      begin
        // It's a real message, find out where in the sequence it's supposed to be, based on timestamp
        NewChatItem := nil;
        cmi := 0;
        while (cmi < ChannelChat.ChatMessages.Count) and (NewChatItem = nil) do
        begin
          if MsgTimestamp < ChannelChat.ChatMessages.Items[cmi].Timestamp then
          begin
            NewChatItem := ChannelChat.ChatMessages.Insert(cmi);
            if ChannelChat.SelectedItem <> nil then ChannelChat.SelectItem(ChannelChat.SelectedItem.Index+1);
          end;
          Inc(cmi);
        end;
        if NewChatItem = nil then
        begin
          NewChatItem := ChannelChat.ChatMessages.Add;
        end;

        if SectionNode.ChildNodes['att_filename'].Text <> '' then
        begin
          MsgMiniature := SectionNode.ChildNodes['miniature'].Text;

          if MsgMiniature <> '' then
          begin

            NewChatItem.Bitmap := TTMSFNCBitmap.Create;
            ImageDataStream := Base64DecodeToStream(MsgMiniature);
            NewChatItem.Bitmap.LoadFromStream(ImageDataStream);
            ImageDataStream.Free;
            NewChatItem.BitmapName := SectionNode.ChildNodes['original_filename'].Text;
          end;
          NewChatItem.Timestamp := MsgTimestamp;
          NewChatItem.Status := GetFileDesc(SectionNode.ChildNodes['original_filename'].Text);

// NewChatItem.Status := SectionNode.ChildNodes['att_filename'].Text; // Save the Attachment_id instead of the message_id
NewChatItem.DataString := SectionNode.ChildNodes['att_filename'].Text;
NewChatItem.Title := MsgSenderRealname;
NewChatItem.Text := MIMEDecodeString(SectionNode.ChildNodes['content'].Text);
NewChatItem.StatusPosition := cisBottomLeft;
end
else
begin
NewChatItem.Timestamp := MsgTimestamp;
NewChatItem.DataString := TheMessageID;
// NewChatItem.Status := 'Message';
NewChatItem.Title := MsgSenderRealname;
NewChatItem.Text := MIMEDecodeString(SectionNode.ChildNodes['content'].Text);
NewChatItem.StatusPosition := cisBottomLeft;
end;

       if MsgSenderUsername = ChannelClient.SSHUser then
       begin
         NewChatItem.MessageLocation := cmlRight;
         NewChatItem.TitleColor := clTeal;
         NewChatItem.Fill.Color := $00FFAD5B;
       end
       else
       begin
         NewChatItem.MessageLocation := cmlLeft;
       end;
      end;
    end
    else
    begin
      if Pos('<chan_msg>',ChannelData) > 0 then
      begin
        // It's a message from the system
        SectionNode := MessageXML.ChildNodes['chan_msg'];
        if SectionNode <> nil then
        begin
          ItemNode := SectionNode.ChildNodes['msg-timestamp'];
          MsgUnixTime := StrToInt64Def(ItemNode.Text,0);
          if MsgUnixTime = 0 then MsgUnixTime := DateTimeToUnix(NOW);
          MsgTimestamp := UnixToDateTime(MsgUnixTime);
          LastMessageTimestamp := MsgUnixTime;
          // Timestamps are in UTC, convert to local time
          MsgTimestamp := MsgTimestamp - (ONE_MINUTE * TZ.Bias) - (ONE_MINUTE * TZ.DaylightBias);

          MsgSenderRealname := MIMEDecodeString(SectionNode.ChildNodes['sender-real-name'].Text);
          MsgSenderUsername := MIMEDecodeString(SectionNode.ChildNodes['sender-username'].Text);

          NewChatItem := ChannelChat.ChatMessages.Add;
          NewChatItem.Timestamp := MsgTimestamp;
          NewChatItem.Status := TheMessageID; // Save the Attachment_id instead of the message_id
          NewChatItem.Title := MsgSenderRealname;
          NewChatItem.Text := MIMEDecodeString(SectionNode.ChildNodes['content'].Text);
          NewChatItem.StatusPosition := cisBottomLeft;
          NewChatItem.MessageLocation := cmlLeft;
          NewChatItem.Fill.Color := clMaroon;
          NewChatItem.TextColor := clCream;
          if Pos('this channel have no retention',lowercase(NewChatItem.Text)) > 0 then ChannelChat.OnVScroll := nil;
          end;
      end;
    end;
    MessageXML.Active := False;
    MessageXML := nil;

// ChannelChat.ChatMessages.EndUpdate;
if (MenuAutoscroll.Checked) and (NewChatItem <> nil) and (NewChatItem.Index > 0) then ChannelChat.ScrollToItem(NewChatItem.Index);
finally

  end;
  ChannelChat.StartReload;
  ChannelChat.StopReload;
  ChannelChat.Refresh;
  if ChannelChat.ChatMessages.Count > 0 then ChannelChat.EnableInteraction;

We tried the code along with a similar formatting from your screenshot.
Can you confirm it's the timestamp only that disappears after a while? It's the only thing we saw breaking, all messages remained visible otherwise.

Yes, I can confirm that when it breaks, it's the bottom of the last message that is missing the timestamp so to say..

Thank you, we are investigating this.

Hi,

We applied some improvements that should resolve this. It will be part of the next update.

Sounds good, when is the next update ?

We are aiming for Thursday this week.

Allrighty, I'll sit tight then :slight_smile: