#include <stdio.h>
#include <string.h>
#include <time.h>
#include <app/Application.h>
#include <stdlib.h>
#include <unistd.h>

#include <app/MessageFilter.h>
#include <interface/Box.h>
#include <interface/Font.h>
#include <interface/ScrollBar.h>
#include <interface/ScrollView.h>
#include <interface/Input.h>

#include <support/Beep.h>

#include "ShareStrings.h"
#include "ChatWindow.h"
#include "Colors.h"
#include "util/StringTokenizer.h"

#include "ReflowingTextView.h"

namespace beshare {

#define CHAT_HISTORY_LENGTH 500   // remember the last 500 items you typed

// Window sizing constraints
#define MIN_WIDTH  600
#define MIN_HEIGHT 280
#define MAX_WIDTH  65535
#define MAX_HEIGHT 65535

// Default window position
#define WINDOW_START_X 30
#define WINDOW_START_Y 100          
#define WINDOW_START_W 640
#define WINDOW_START_H 400          

#define TEXT_ENTRY_HEIGHT 20

ChatWindow :: ChatWindow(BRect defRect, const char * title, window_look look, window_feel feel, uint32 flags) : BWindow(defRect, title, look, feel, flags), _isInitialized(false), _isScrolling(false), _fontSize(0.0f)
{
   PostMessage(CHATWINDOW_COMMAND_INIT);
}


ChatWindow :: ~ChatWindow()
{
   // empty?
}



void
ChatWindow ::
LogHelp(const char * cmd, int tokenID, int descID, ChatWindow * optEchoTo)
{
   String s("   /");
   s += cmd;
   s += " ";
   if (tokenID >= 0) s += str(tokenID);
   s += " - ";
   s += str(descID);
   LogMessage(LOG_INFORMATION_MESSAGE, s.Cstr(), NULL, NULL, false, optEchoTo);
}


class PasteMessageFilter : public BMessageFilter
{
public:
   PasteMessageFilter(uint32 command, BTextControl * control) : BMessageFilter(B_PASTE), _command(command), _control(control) {/* empty */}

   virtual filter_result Filter(BMessage *message, BHandler **)
   {
      filter_result ret = B_DISPATCH_MESSAGE;

      if (message->what == B_PASTE)
      {
         if (be_clipboard->Lock())
         {
            BMessage * data = be_clipboard->Data();
            const char * text;
            ssize_t textLen;
            if (data->FindData("text/plain", B_MIME_TYPE, (const void **) &text, &textLen) == B_NO_ERROR)
            {
               if ((strchr(text, '\r'))||(strchr(text, '\n')))
               {
                  String temp(_control->Text());
                  if (temp.Length() == 0) temp += "\n";

                  // gotta do this shit because text isn't terminated, sigh
                  char * s = new char[textLen+1];
                  memcpy(s, text, textLen);
                  s[textLen] = '\0';
                  temp += s;
                  delete [] s;

                  BMessage sendMsg(_command);
                  sendMsg.AddString("text", temp.Cstr());
                  _control->Window()->PostMessage(&sendMsg);
                  ret = B_SKIP_MESSAGE;
               }
            }
            be_clipboard->Unlock();
         }
      }
      return ret;
   }

private:
   uint32 _command;
   BTextControl * _control;
};

void ChatWindow :: MessageReceived(BMessage * msg)
{
   switch(msg->what)
   {
      case CHATWINDOW_COMMAND_SET_CUSTOM_TITLE:
      {
         const char * title;
         if (msg->FindString("title", &title) != B_NO_ERROR) title = "";
         SetCustomWindowTitle(title);
         UpdateTitleBar();
      }
      break;

      case CHATWINDOW_COMMAND_INIT:
      {
         BRect chatViewBounds = GetChatView()->Bounds();

         BRect chatTextBounds(0, 0, chatViewBounds.Width(), chatViewBounds.Height()-(TEXT_ENTRY_HEIGHT+5.0f));
         BBox * box = new BBox(chatTextBounds, NULL, B_FOLLOW_ALL_SIDES);
         GetChatView()->AddChild(box);        

         _chatText = new ReflowingTextView(BRect(2,2,chatTextBounds.Width()-(2+B_V_SCROLL_BAR_WIDTH), chatTextBounds.Height()-2), NULL, chatTextBounds, B_FOLLOW_ALL_SIDES);

         _chatText->MakeEditable(false);
         _chatText->SetStylable(true);

         _chatScrollView = new BScrollView(NULL, _chatText, B_FOLLOW_ALL_SIDES, 0L, false, true, B_FANCY_BORDER);
         box->AddChild(_chatScrollView);

         String chat(str(STR_CHAT_VERB));
         chat += ':';
         _textEntry = new BTextControl(BRect(0, chatViewBounds.Height()-TEXT_ENTRY_HEIGHT, chatViewBounds.Width(), chatViewBounds.Height()), NULL, chat.Cstr(), NULL, NULL, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM);

         _textEntry->TextView()->AddFilter(new PasteMessageFilter(CHATWINDOW_COMMAND_SEND_CHAT_TEXT, _textEntry));
         _textEntry->SetDivider(_textEntry->StringWidth(chat.Cstr())+4.0f);
         _textEntry->SetViewColor(BeBackgroundGrey);
         GetChatView()->AddChild(_textEntry);

         _textEntry->SetTarget(this);
         _textEntry->MakeFocus();
         _isInitialized = true;
         break;
      }

      case CHATWINDOW_COMMAND_SET_COMMAND_TARGET:
      {
         BMessenger target;
         BMessage queryMessage;
         BMessage privMessage;
         if ((msg->FindMessenger("target", &target) == B_NO_ERROR)&&(msg->FindMessage("querymessage", &queryMessage) == B_NO_ERROR)&&(msg->FindMessage("privmessage", &privMessage) == B_NO_ERROR)) _chatText->SetCommandURLTarget(target, queryMessage, privMessage);
         break;
      }

      case CHATWINDOW_COMMAND_SEND_CHAT_TEXT:
      {
         String text;
         {
            const char * temp;
            if (msg->FindString("text", &temp) == B_NO_ERROR) text = temp;
            else
            {
               text = _textEntry->Text();
               text = text.Trim();
            }
         }

         if (text.Length() > 0)
         {
            if ((_chatHistory.GetNumItems() == 0)||(_chatHistory.GetItemPointer(_chatHistory.GetNumItems()-1)->Equals(text) == false))
            {
               _chatHistory.AddTail(text);
               if (_chatHistory.GetNumItems() > CHAT_HISTORY_LENGTH) _chatHistory.RemoveHead();
            }
            _chatHistoryPosition = -1;  // reset to bottom of list for next time                      
            SendChatText(text, this);
         }
         _textEntry->SetText("");
         break;
      }
      
      default:
         BWindow::MessageReceived(msg);
      break;
   }
}


void 
ChatWindow ::
ChatTextReceivedBeep(bool isPersonal, bool mentionsName)
{
   if ((isPersonal)||(mentionsName)) DoBeep(isPersonal ? SYSTEM_SOUND_PRIVATE_MESSAGE_RECEIVED : SYSTEM_SOUND_USER_NAME_MENTIONED);
}

void
ChatWindow ::
LogMessage(LogMessageType type, const char * inText, const char * optSessionID, const rgb_color * inOptTextColor, bool isPersonal, ChatWindow * optEchoTo)
{
   if ((optEchoTo)&&(optEchoTo != this)) 
   {  
      optEchoTo->LogMessage(type, inText, optSessionID, inOptTextColor, isPersonal, NULL);
      return;
   }

   for (LogDestinationType d = DESTINATION_DISPLAY; d<NUM_DESTINATIONS; d = (LogDestinationType)(((int)d)+1))
   {
      const char * text = inText;
      const rgb_color * optTextColor = inOptTextColor;

      if (OkayToLog(type, d, isPersonal))
      {
         const char * preamble = "???"; 
         const rgb_color black = {0, 0, 0, 255};
         rgb_color color = black;  // default
         String temp;
         int startRed = -1;
         int redLen = 0;
         
         if (optTextColor == NULL) optTextColor = &black;

         switch(type)
         {
            case LOG_INFORMATION_MESSAGE:
            case LOG_USER_EVENT_MESSAGE:
            case LOG_UPLOAD_EVENT_MESSAGE:
               preamble = str(STR_SYSTEM);
               color.blue = 128;
            break;

            case LOG_WARNING_MESSAGE:
               preamble = str(STR_WARNING);
               color.blue = color.green = 128;
            break;

            case LOG_ERROR_MESSAGE:
               preamble = str(STR_ERROR);
               color.red = 128;
            break;

            case LOG_LOCAL_USER_CHAT_MESSAGE:
               if ((strncmp(text, "/me ", 4) == 0)||(strncmp(text, "/me\'", 4) == 0))
               {
                  preamble = str(STR_ACTION);
                  
                  GetLocalUserName(temp);
                  temp += &text[3];
                  text = temp.Cstr();
                  color.red = color.blue = 128;
               }     
               else
               {
                  String t2;
                  if (ShowUserIDs(d))
                  {
                     temp = "(";
                     GetLocalSessionID(t2);
                     temp += t2;
                     temp += ") ";
                  }

                  GetLocalUserName(t2);
                  temp += t2;
                  if ((optSessionID)&&(ShowMessageTargets()))
                  {
                     temp += "-> (";
                     temp += optSessionID;
                     temp += "/";
                     GetUserNameForSession(optSessionID, t2);
                     temp += t2; 
                     temp += ") ";
                  }
                  preamble = temp.Cstr();
                  color.green = 128;
               }
            break;

            case LOG_REMOTE_USER_CHAT_MESSAGE:
               if (optSessionID)
               {
                  if ((strncmp(text, "/me ", 4) == 0)||(strncmp(text, "/me\'", 4) == 0))
                  {
                     preamble = str(STR_ACTION);
                     GetUserNameForSession(optSessionID, temp);
                     temp += &text[3];
                     text = temp.Cstr();
                     color.red = color.blue = 128;
                  }
                  else
                  {
                     temp = "(";
                     temp += optSessionID;
                     temp += ") ";
                     String t2;
                     GetUserNameForSession(optSessionID, t2);
                     temp += t2;
                     preamble = temp.Cstr();
                     color.green = 1;  // if I don't put this, the text doesn't show up???
                  }
               }

               {
                  // See if we were mentioned as part of the comment.
                  // Only look for the first part of our username (before any
                  // spaces or punctuation), as this is what people will often
                  // shorten our name to.
                  String shortName;
                  GetLocalUserName(shortName);
                  const unsigned char * orig = (const unsigned char *) shortName.Cstr();
                  const unsigned char * temp = orig;
                  while(((*temp >= 'A')&&(*temp <= 'Z'))||
                        ((*temp >= 'a')&&(*temp <= 'z'))||
                        ((*temp >= '0')&&(*temp <= '9'))||
                        (*temp == '_')||
                        (*temp >= 0x80)) temp++;
                  if (temp > orig) shortName = shortName.Substring(0, temp-orig);
                   
                  String iText(text);
                  iText = iText.ToUpperCase();
                  shortName = shortName.ToUpperCase();
                  startRed = iText.IndexOf(shortName.Prepend(" "));  // only counts if it's the start of a word!
                  if (startRed >= 0) startRed++;
                                else startRed = (iText.StartsWith(shortName)) ? 0 : -1;  // or if it's the start of text
                  if (startRed >= 0) 
                  {
                     redLen = shortName.Length();

                     // See if we can't extend the red back out to the original name length....
                     String temp;
                     GetLocalUserName(temp);
                     temp = temp.ToUpperCase(); 
                     const char * c1 = &iText.Cstr()[startRed+redLen];
                     const char * c2 = &temp.Cstr()[redLen];
                     while((c1)&&(c2)&&(*c1 == *c2))
                     {
                        c1++;
                        c2++;
                        redLen++;
                     }
                  }
               }
               if (d == DESTINATION_DISPLAY) ChatTextReceivedBeep(isPersonal, (startRed >= 0));
            break;

            case NUM_LOG_MESSAGE_TYPES:
               // won't happen, this clause is only here to avoid a compiler warning
            break;
         }

         text_run_array style;
         style.count = 1;
         style.runs[0].offset = 0;
         style.runs[0].font = *be_bold_font;
         if (_fontSize != 0.0f) style.runs[0].font.SetSize(_fontSize);
         style.runs[0].color = black;

         // Figure out whether we should scroll down BEFORE inserting text;
         // this way if the user is inserting lots of text it won't affect our decision.
         bool scrollDown = IsScrollBarNearBottom();

         if (ShowTimestamps(d))
         {
            time_t n = time(NULL);
            struct tm * now = localtime(&n);
            char buf[128];
            sprintf(buf, "[%i/%i %i:%02i] ", now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min);
            InsertChatText(d, buf, strlen(buf), &style);
         }

         style.runs[0].color = color;
         InsertChatText(d, preamble, strlen(preamble), &style);

         // now back to black
         style.runs[0].color = *optTextColor;
         style.runs[0].font = *be_plain_font;
         if (_fontSize != 0.0f) style.runs[0].font.SetSize(_fontSize);

         InsertChatText(d, ": ", 2, &style);
         if ((startRed >= 0)&&(redLen > 0))
         {
            InsertChatText(d, text, startRed, &style);
            const rgb_color red = {255, 128, 0, 255};
            style.runs[0].color = red;
            InsertChatText(d, text+startRed, redLen, &style);
            style.runs[0].color = *optTextColor;
            int slen = strlen(text)-(startRed+redLen);
            if (slen > 0) InsertChatText(d, text+startRed+redLen, slen, &style);
         }
         else InsertChatText(d, text, strlen(text), &style);

         InsertChatText(d, "\n", 1, &style);

         if ((scrollDown)&&(_isScrolling == false)) ScrollToBottom();
      }
   }
}


void ChatWindow :: ScrollToBottom()
{
   BScrollBar * sb = _chatScrollView->ScrollBar(B_VERTICAL);
   if (sb)
   {
      float min, max, smallStep, bigStep;
      sb->GetRange(&min, &max);
      sb->GetSteps(&smallStep, &bigStep);
      sb->SetValue(max-smallStep);
   }
}

void ChatWindow :: ScrollToTop()
{
   if (_isScrolling) return;
   BScrollBar * sb = _chatScrollView->ScrollBar(B_VERTICAL);
   if (sb)
   {
      float min, max;
      sb->GetRange(&min, &max);
      sb->SetValue(min);
   }
}



// This callback handles the actual insertion of text into an output destination.
// The ChatWindow version only handles insertion of text into the BTextView.
void
ChatWindow ::
InsertChatText(LogDestinationType dest, const char * t, int textLen, text_run_array * optStyle)
{
   if (dest == DESTINATION_DISPLAY)
   {
      if (textLen <= 0) return;

      text_run_array defaultStyle;
      const rgb_color black = {0, 0, 0, 255};
      defaultStyle.count = 1;
      defaultStyle.runs[0].offset = 0;
      defaultStyle.runs[0].font = *be_plain_font;
      if (_fontSize != 0.0f) defaultStyle.runs[0].font.SetSize(_fontSize);
      defaultStyle.runs[0].color = black;
      if (optStyle == NULL) optStyle = &defaultStyle;

      Queue<String> urls;

      // Get only the specified substring...
      String text(t);
      if ((int)text.Length() > textLen) text = text.Substring(0, textLen);

      // Chop the text into words (ok because spaces aren't allowed in URLs) and check each one
      StringTokenizer tok(text.Cstr(), " \r\n");
      const char * n;
      while((n = tok.GetNextToken()) != NULL)
      {
         String next(n);

         if ((next.StartsWithIgnoreCase("file://")) ||(next.StartsWithIgnoreCase("http://"))||
             (next.StartsWithIgnoreCase("https://"))||(next.StartsWithIgnoreCase("mailto:"))||
             (next.StartsWithIgnoreCase("ftp://"))  ||(next.StartsWithIgnoreCase("audio://"))||
             (next.StartsWithIgnoreCase("beshare:"))||(next.StartsWithIgnoreCase("priv:"))||
             (next.StartsWithIgnoreCase("share:")))  // v2.11:  synonym for beshare:
         {
            if ((next.StartsWithIgnoreCase("beshare:") == false)&&(next.StartsWithIgnoreCase("share:") == false))
            {
               // Remove any non-alphanumeric chars from the end of the URL.
               while(next.Length() > 1)
               {
                  char last = next[next.Length()-1];
                  if (((last >= '0')&&(last <= '9')) ||
                      ((last >= 'a')&&(last <= 'z')) ||
                      ((last >= 'A')&&(last <= 'Z')) ||
                      (last == '/')) break;
                  else next = next.Substring(0, next.Length()-1);
               }
            }
            urls.AddTail(next);
         }
      }

      if (urls.GetNumItems() > 0)
      {
         // Put the URLs in blue underline...
         text_run_array urlStyle;
         const rgb_color blue = {0, 0, 255, 255};
         urlStyle.count = 1;
         urlStyle.runs[0].offset = 0;
         urlStyle.runs[0].font = optStyle->runs[0].font;
         if (_fontSize != 0.0f) urlStyle.runs[0].font.SetSize(_fontSize);
         urlStyle.runs[0].color = blue;

         String url;
         while(urls.RemoveHead(url) == B_NO_ERROR)
         {
            // Find the next URL...
            int urlIndex = text.IndexOf(url);
            if (urlIndex > 0)
            {
               // output everything before the URL in the normal style...
               InsertChatTextAux(text.Cstr(), urlIndex, optStyle);
               text = text.Substring(urlIndex);
            }     

            // Then output the URL itself
            _chatText->AddURLRegion(_chatText->TextLength(), url.Length());
            InsertChatTextAux(url.Cstr(), url.Length(), &urlStyle);
            text = text.Substring(url.Length());
         }

         if (text.Length() > 0) InsertChatTextAux(text.Cstr(), text.Length(), optStyle);
      }
      else InsertChatTextAux(t, textLen, optStyle);
   }
}

void
ChatWindow ::
InsertChatTextAux(const char * text, int textLen, text_run_array * optStyle)
{
   if (text)
   {
      if (optStyle) _chatText->Insert(_chatText->TextLength(), text, textLen, optStyle);
               else _chatText->Insert(_chatText->TextLength(), text, textLen);
   }
}

 
void 
ChatWindow ::
DispatchMessage(BMessage * msg, BHandler * target)
{
   if (msg->what == B_KEY_DOWN)
   {
      int8 c;
      int32 mod;
      if ((msg->FindInt8("byte", &c) == B_NO_ERROR)&&(msg->FindInt32("modifiers", &mod) == B_NO_ERROR)&&((c == B_ENTER)||((mod & B_LEFT_COMMAND_KEY) == 0)))
      {
         BTextView * tv = dynamic_cast<BTextView *>(target);
         
         if ((tv)&&(tv->IsEditable()))
         {
            if (tv == _textEntry->TextView()) 
            {
               if (c == B_ENTER) ScrollToBottom();
               UserChatted();
            }

                 if (c == B_ENTER) PostMessage(CHATWINDOW_COMMAND_SEND_CHAT_TEXT);
            else if ((_textEntry->Text()[0])&&(c == B_TAB)&&((mod & B_CONTROL_KEY) == 0))  // don't override the Twitcher!
            {
               String completed;
               if (DoTabCompletion(_textEntry->Text(), completed, NULL) == B_NO_ERROR)
               {
                  _textEntry->SetText(completed.Cstr());
                  _textEntry->TextView()->Select(completed.Length(), completed.Length());  // move cursor to EOL
               }
               else DoBeep(SYSTEM_SOUND_AUTOCOMPLETE_FAILURE);

               msg = NULL;  // don't do anything further with the message!
            }
            else 
            {
               int lineDiff = 0;
               float scrollDiff = 0.0; // for scrolling the chat-window, like in Terminal - added by Hugh
               float scrollLine = _chatText->LineHeight();
               float scrollPage = _chatText->Bounds().Height() - scrollLine;  // scroll one line less for better orientation
               
               switch(c)
               {
                  case B_UP_ARROW:
                          if (mod & B_SHIFT_KEY)   lineDiff   = -CHAT_HISTORY_LENGTH;
                     else if (mod & B_CONTROL_KEY) scrollDiff = -scrollLine;   // scroll up one line
                     else                          lineDiff   = -1;
                  break;

                  case B_DOWN_ARROW:
                          if (mod & B_SHIFT_KEY)   lineDiff   = CHAT_HISTORY_LENGTH;
                     else if (mod & B_CONTROL_KEY) scrollDiff = scrollLine;   // scroll down one line
                     else                          lineDiff   = 1;
                  break;

                  case B_PAGE_UP:
                          if (mod & B_SHIFT_KEY)   lineDiff   = -10;   // necessary?
                     else if (mod & B_CONTROL_KEY) scrollDiff = -scrollPage;   // scroll up one page (-1 line)
                  break;

                  case B_PAGE_DOWN:
                          if (mod & B_SHIFT_KEY)   lineDiff   = 10;   // necessary?
                     else if (mod & B_CONTROL_KEY) scrollDiff = scrollPage;   // scroll down one page
                  break;

                  case B_HOME:
                     if (mod & B_CONTROL_KEY)
                     {
                        _chatText->ScrollTo(0.0, 0.0);
                        msg = NULL;
                     }
                  break;

                  case B_END:
                     if (mod & B_CONTROL_KEY)
                     {
                        _chatText->ScrollTo(0.0, _chatText->TextRect().bottom - _chatText->Bounds().Height());
                        msg = NULL;
                     }
                  break;
               }
                     
               if ((lineDiff)&&(_chatHistory.GetNumItems() > 0))
               {
                  int numItems = _chatHistory.GetNumItems();
                  if (_chatHistoryPosition == -1)
                  {
                     if (lineDiff < 0) _chatHistoryPosition = numItems;
                                  else lineDiff = 0;  // at bottom + down arrow = no effect
                  }

                  if (lineDiff)
                  {
                     _chatHistoryPosition += lineDiff;
                     if (_chatHistoryPosition < 0) _chatHistoryPosition = 0;
                     if (_chatHistoryPosition >= numItems) 
                     {
                        _chatHistoryPosition = -1;
                        _textEntry->SetText("");
                     }
                     else
                     {
                        const String * s = _chatHistory.GetItemPointer(_chatHistoryPosition);
                        _textEntry->SetText(s->Cstr());
                        _textEntry->TextView()->Select(s->Length(), s->Length());
                     }
                     msg = NULL;  // don't do anything further with the message!
                  }
               }
               else if (scrollDiff)
               {
                  // check bounds
                  float view_y = _chatText->Bounds().top;               
                  float max_y  = _chatText->TextRect().bottom - _chatText->Bounds().Height();

                  view_y += scrollDiff;   // move view

                  // limit movement to bounds
                  if (view_y < 0.0)   view_y = 0.0;
                  if (view_y > max_y) view_y = max_y;

                  _chatText->ScrollTo(0.0, view_y);

                  msg = NULL;
               }
            }
         }
         else
         {
                 if ((c == B_ENTER)||((tv == _chatText)&&(c == B_END))) {ScrollToBottom(); msg = NULL;}
            else if ((tv == _chatText)&&(c == B_HOME)) {ScrollToTop(); msg = NULL;}
            else if (c >= ' ')
            {
               if ((_isScrolling == false)&&(!_textEntry->IsFocus())) ScrollToBottom();
               _textEntry->MakeFocus();

               String s(_textEntry->Text());
               s += c;
               _textEntry->SetText(s.Cstr());
               _textEntry->TextView()->Select(s.Length(), s.Length());
               msg = NULL;  // don't do anything further with the message!
               UserChatted();
            }
         }
      }
   }
   else if ((msg->what == B_MOUSE_DOWN)&&(target == _chatText)&&(_chatText->IsFocus() == false)) _chatText->MakeFocus();
   else if (msg->what == B_SIMPLE_DATA)
   {
      const char * u;
      if (msg->FindString("be:url", &u) == B_NO_ERROR) 
      {
         BTextView * tv = _textEntry->TextView();
         const char * t = tv->Text();
         if ((tv->TextLength() > 0)&&(t[tv->TextLength()-1] != ' ')) tv->Insert(tv->TextLength(), " ", 1);
         tv->Insert(tv->TextLength(), u, strlen(u));
         _textEntry->MakeFocus();
         msg = NULL;
      }
   }

   if (msg) BWindow::DispatchMessage(msg, target);

   if (_isInitialized)
   {
      bool isScrolling = (IsScrollBarNearBottom() == false);
      if (isScrolling != _isScrolling)
      {
         _isScrolling = isScrolling;
         uint8 level = isScrolling ? 245 : 255;
         _chatText->SetViewColor(level, 255, 255);
         _chatText->Invalidate();
      }
   }
}


void 
ChatWindow ::
SetCommandURLTarget(const BMessenger & target, const BMessage & queryMsg, const BMessage & privMsg)
{
   BMessage setQueryTarget(CHATWINDOW_COMMAND_SET_COMMAND_TARGET);
   setQueryTarget.AddMessenger("target", target);
   setQueryTarget.AddMessage("querymessage", &queryMsg);
   setQueryTarget.AddMessage("privmessage", &privMsg);
   PostMessage(&setQueryTarget);
}


void
ChatWindow ::
DoBeep(const char * which) const
{
#ifdef B_BEOS_VERSION_5
   (void) system_beep(which);
#else
   beep();
#endif
}

void
ChatWindow ::
UserChatted()
{
   // empty
}

bool ChatWindow :: IsScrollBarNearBottom() const 
{
   bool scrollDown = false;
   if (_chatScrollView)
   {
      BScrollBar * sb = _chatScrollView->ScrollBar(B_VERTICAL);
      if (sb) 
      {
         float min, max, smallStep, bigStep;
         sb->GetRange(&min, &max);
         sb->GetSteps(&smallStep, &bigStep);
         scrollDown = (sb->Value() >= max-(smallStep*10));
      }
   }
   return scrollDown;
}

void ChatWindow :: SetFontSize(const String & cmdString)
{
   float fs = (cmdString.Length() > 9) ? atof(cmdString.Cstr()+10) : 0.0f;
   if (fs < 0.0f) fs = 0.0f;
   SetFontSize(fs);
   if (fs > 0.0f)
   {
      char buf[64];
      sprintf(buf, " %.0f", fs);
      String s(str(STR_FONT_SIZE_SET_TO));
      s += buf;
      LogMessage(LOG_INFORMATION_MESSAGE, s.Cstr(), NULL, NULL, false, this);
   }
   else LogMessage(LOG_INFORMATION_MESSAGE, str(STR_FONT_SIZE_RESET_TO_DEFAULT), NULL, NULL, false, this);
}

};  // end namespace beshare
