Commits

Anonymous committed 451dbef Draft

Implemented room chat logging.

Comments (0)

Files changed (10)

engine/ClientLogic.cpp

 static const String s_mucPrivChat = "room_member_chat";
 static const String s_mucKick = "room_member_kick";
 static const String s_mucBan = "room_member_ban";
+static const String s_mucRoomShowLog = "room_showlog";
+static const String s_mucMemberShowLog = "room_member_showlog";
 // Not selected string(s)
 static String s_notSelected = "-none-";
 // Maximum number of call log entries
 
 // Request to the client to log a chat entry
 static bool logChat(ClientContact* c, unsigned int time, bool send, bool delayed,
-    const String& body)
+    const String& body, bool roomChat = true, const String& nick = String::empty())
 {
     if (!c)
 	return false;
 	return false;
     if (!Client::self())
 	return false;
+    MucRoom* room = c->mucRoom();
     NamedList p("");
     p.addParam("account",c->accountName());
     p.addParam("contact",c->uri());
-    p.addParam("contactname",c->m_name);
-    p.addParam("sender",send ? "" : c->m_name.c_str());
+    if (!room) {
+	p.addParam("contactname",c->m_name);
+	p.addParam("sender",send ? "" : c->m_name.c_str());
+    }
+    else {
+	p.addParam("muc",String::boolText(true));
+	p.addParam("roomchat",String::boolText(roomChat));
+	p.addParam("contactname",roomChat ? room->resource().m_name : nick);
+	p.addParam("sender",send ? "" : nick.c_str());
+    }
     p.addParam("time",String(time));
     p.addParam("send",String::boolText(send));
     if (!send && delayed)
 }
 
 // Show contact archive log
-static bool logShow(ClientContact* c)
+static bool logShow(ClientContact* c, bool roomChat = true,
+    const String& nick = String::empty())
 {
     if (!(c && Client::self()))
 	return false;
+    MucRoom* room = c->mucRoom();
     NamedList p("");
     p.addParam("account",c->accountName());
     p.addParam("contact",c->uri());
+    if (room) {
+	p.addParam("muc",String::boolText(true));
+	p.addParam("roomchat",String::boolText(roomChat));
+	p.addParam("contactname",nick,false);
+    }
     return Client::self()->action(0,"archive:showchat",&p);
 }
 
 // Close archive session
-static bool logCloseSession(ClientContact* c)
+static bool logCloseSession(ClientContact* c, bool roomChat = true,
+    const String& nick = String::empty())
 {
     if (!(c && Client::self()))
 	return false;
+    MucRoom* room = c->mucRoom();
     NamedList p("");
     p.addParam("account",c->accountName());
     p.addParam("contact",c->uri());
+    if (room) {
+	p.addParam("muc",String::boolText(true));
+	p.addParam("roomchat",String::boolText(roomChat));
+	p.addParam("contactname",nick,false);
+    }
     return Client::self()->action(0,"archive:closechatsession",&p);
 }
 
     return Client::self()->action(0,"archive:clearaccountnow",&p);
 }
 
+// Close all MUC log sessions of a room
+static void logCloseMucSessions(MucRoom* room)
+{
+    if (!room)
+	return;
+    Window* w = room->getChatWnd();
+    if (w) {
+	NamedList p("");
+	Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,w);
+	unsigned int n = p.length();
+	for (unsigned int i = 0; i < n; i++) {
+	    NamedString* ns = p.getParam(i);
+	    if (!(ns && ns->name()))
+		continue;
+	    MucRoomMember* m = room->findMemberById(ns->name());
+	    if (m)
+		logCloseSession(room,false,m->m_name);
+	}
+    }
+    else {
+	for (ObjList* o = room->resources().skipNull(); o; o = o->skipNext()) {
+	    MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
+	    logCloseSession(room,false,m->m_name);
+	}
+    }
+    logCloseSession(room);
+}
+
 // Update protocol related page(s) in account edit/add or wizard
 static void selectProtocolSpec(NamedList& p, const String& proto, bool advanced,
     const String& protoList)
     pRoom->addParam("item:" + s_mucChgSubject,"");
     pRoom->addParam("item:","");
     pRoom->addParam("item:" + s_mucInvite,"");
+    pRoom->addParam("item:","");
+    pRoom->addParam("item:" + s_mucRoomShowLog,"");
     tmp.addParam(new NamedPointer("setmenu",pRoom,""));
     // Members context menu
     menuName << "_" << s_mucMembers;
     pMembers->addParam("item:","");
     pMembers->addParam("item:" + s_mucKick,"");
     pMembers->addParam("item:" + s_mucBan,"");
+    pMembers->addParam("item:","");
+    pMembers->addParam("item:" + s_mucMemberShowLog,"");
     NamedList* p = new NamedList("");
     p->addParam(new NamedPointer("contactmenu",pMembers));
     tmp.addParam(new NamedPointer("setparams:" + s_mucMembers,p));
 		ObjList* o = m_accounts->accounts().skipNull();
 		for (; o; o = o->skipNext()) {
 		    ClientAccount* acc = static_cast<ClientAccount*>(o->get());
+		    for (ObjList* l = acc->mucs().skipNull(); l; l = l->skipNext())
+			logCloseMucSessions(static_cast<MucRoom*>(l->get()));
 		    acc->mucs().clear();
 		}
 		// Remove from pending chat
 	    if (p) {
 		room->addChatHistory(id,!delay ? "chat_in" : "chat_delayed",p);
 		notifyIncomingChat(room,id);
-		room->flashChat(id);
+		if (body)
+		    logChat(room,time,false,delay != 0,body,mucChat,nick);
 	    }
 	    if (resetNotif)
 		room->setChatProperty(id,"history","_yate_tempitemcount",String((int)0));
 			}
 		    }
 		}
+		logCloseMucSessions(room);
 		TelEngine::destruct(room);
 		return true;
 	    }
+	    else if (room) {
+		MucRoomMember* m = room->findMemberById(item);
+		if (m)
+		    logCloseSession(room,false,m->m_name);
+	    }
 	}
 	if (wnd && wnd->id() == ClientContact::s_dockedChatWnd) {
 	    if (!s_changingDockedChat)
 	    else
 		ok = text && room->sendChat(text,m->m_name);
 	    if (ok) {
-		NamedList* tmp = buildChatParams(text,"me",Time::secNow());
+		unsigned int time = Time::secNow();
+		NamedList* tmp = buildChatParams(text,"me",time);
 		room->setChatProperty(id,"history","_yate_tempitemreplace",String(false));
 		room->addChatHistory(id,"chat_out",tmp);
 		room->setChatProperty(id,"history","_yate_tempitemreplace",String(true));
 		room->setChatInput(id);
+		logChat(room,time,true,false,text,room->ownMember(m),m->m_name);
 	    }
 	}
 	else
 	showMucInvite(*room,m_accounts);
 	return true;
     }
+    if (getPrefixedContact(name,s_mucRoomShowLog,id,m_accounts,0,&room)) {
+	// Show MUC room log
+	if (!room)
+	    return false;
+	logShow(room,true);
+	return true;
+    }
+    if (getPrefixedContact(name,s_mucMemberShowLog,id,m_accounts,0,&room)) {
+	// Show MUC room member log
+	MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
+	if (!member)
+	    return false;
+	logShow(room,room->ownMember(member),member->m_name);
+	return true;
+    }
     bool kick = getPrefixedContact(name,s_mucKick,id,m_accounts,0,&room);
     if (kick || getPrefixedContact(name,s_mucBan,id,m_accounts,0,&room)) {
 	MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
 	String text;
 	if (room->ownMember(member))
 	    text << "You are";
-	else
+	else {
 	    text << member->m_name << " is";
+	    // Close old member's chat log
+	    logCloseSession(room,false,member->m_name);
+	}
 	text << " now known as " << nick;
 	addChatNotify(*room,text,msg.msgTime().sec());
 	member->m_name = nick;

modules/qt4/clientarchive.cpp

     };
     // Init object
     ChatFile(const String& dir, const String& fileName);
+    // Retrieve the file type
+    inline char type() const
+	{ return m_type; }
     // Retrieve the file account. Lock it before use
     inline const String& account() const
 	{ return m_account; }
     // Retrieve the file contact display name. Lock it before use
     inline const String& contactDisplayName() const
 	{ return m_contactName ? m_contactName : m_contact; }
+    // Retrieve the id of the room owning a private chat. Lock it before use
+    inline const String& roomId() const
+	{ return m_roomId; }
     // Retrieve the file sessions. Lock it before use
     inline const ObjList& sessions() const
 	{ return m_sessions; }
     String m_account;
     String m_contact;
     String m_contactName;
+    String m_roomId;                     // Parent room id if this is a private room chat
     String m_fileName;
     String m_full;
     File m_file;
     // Refresh the list. Re-load all archive
     void refresh();
     // Clear all
-    void clear();
+    void clear(bool memoryOnly);
     // Clear all logs belonging to a given account
     void clearAccount(const String& account, ObjList& removedItems);
     // Remove an item and it's file
     //  session was loaded into memory
     ChatFile* closeChat(const NamedList& params);
     // Build a file name from a list of parameters
+    static inline void buildChatFileName(String& buf, char type, const String& account,
+	const String& contact, const String& nick = String::empty());
+    // Build a file name from a list of parameters
     static inline bool buildChatFileName(String& buf, const NamedList& params);
 protected:
     bool m_loaded;                       // Archive loaded
     return String::empty();
 }
 
+// Retrieve the UI item type from chat file type
+static inline const char* uiItemType(char type)
+{
+    if (type == MARKUP_CHAT)
+	return "chat";
+    if (type == MARKUP_ROOMCHAT)
+	return "roomchat";
+    return "roomprivchat";
+}
+
 // Find 2 NULL values in a buffer. Return buffer len if not found
 unsigned int find2Null(unsigned char* buf, unsigned int len)
 {
     appendString(buf,tmp);
 }
 
+// Build chat file UI params
+static NamedList* chatFileUiParams(ChatFile* f)
+{
+    if (!f)
+	return 0;
+    Lock lock(f);
+    NamedList* upd = new NamedList(f->toString());
+    upd->addParam("item_type",uiItemType(f->type()));
+    upd->addParam("account",f->account());
+    upd->addParam("contact",f->contact());
+    if (f->type() == MARKUP_CHAT)
+	upd->addParam("name",f->contactDisplayName());
+    else if (f->type() == MARKUP_ROOMCHAT)
+	upd->addParam("name",f->contact());
+    else {
+        upd->addParam("parent",f->roomId());
+	upd->addParam("name",f->contactDisplayName());
+    }
+    return upd;
+}
+
 // Build a chat session UI params
 static NamedList* chatSessionUiParams(ChatSession* s)
 {
     }
     else if (!(params && writeFileHeader(*params,error)))
 	return false;
+    m_roomId.clear();
+    // Build the room id if this is a private chat
+    if (m_type == MARKUP_ROOMCHATPRIVATE)
+	ChatArchive::buildChatFileName(m_roomId,MARKUP_ROOMCHAT,m_account,m_contact);
     return true;
 }
 
 {
     Lock lock(this);
     m_loaded = true;
-    m_items.clear();
     unsigned int n = m_index.sections();
     for (unsigned int i = 0; i < n; i++) {
 	if (exiting())
 }
 
 // Clear all
-void ChatArchive::clear()
+void ChatArchive::clear(bool memoryOnly)
 {
     Lock lock(this);
     m_items.clear();
+    if (memoryOnly)
+	return;
     unsigned int n = m_index.sections();
     for (unsigned int i = 0; i < n; i++) {
 	NamedList* f = m_index.getSection(i);
 	return 0;
     }
     f->lock();
-    m_index.setValue(fn,"type",String(f->m_type));
+    m_index.setValue(fn,"type",String(f->type()));
     m_index.setValue(fn,"account",f->account());
     m_index.setValue(fn,"contact",f->contact());
     if (f->contactName() && f->contactName() != m_index.getValue(fn,"contactname"))
 	m_index.setValue(fn,"contactname",f->m_contactName);
+    if (f->type() != MARKUP_ROOMCHATPRIVATE)
+	m_index.clearKey(fn,"room");
+    else
+	m_index.setValue(fn,"room",f->roomId());
     f->unlock();
     m_index.save();
     m_items.append(f);
 }
 
 // Build a file name from a list of parameters
+void ChatArchive::buildChatFileName(String& buf, char type, const String& account,
+    const String& contact, const String& nick)
+{
+    buf = "chat_";
+    buf << account.hash() << "_" << String(contact).toLower().hash();
+    if (type == MARKUP_ROOMCHATPRIVATE)
+	buf << "_" << nick.hash();
+    buf << "_" << type;
+}
+
+// Build a file name from a list of parameters
 bool ChatArchive::buildChatFileName(String& buf, const NamedList& params)
 {
     const String& account = params["account"];
-    String contact = params["contact"];
+    const String& contact = params["contact"];
     if (!(account && contact))
 	return false;
-    buf = "chat_";
-    buf << String(account.hash()) << "_" << contact.toLower().hash();
     char type = chatType(params);
-    if (type == MARKUP_ROOMCHATPRIVATE)
-	buf << "_" << params["nick"].hash();
-    buf << "_" << type;
+    const String& nick = (type != MARKUP_ROOMCHATPRIVATE) ?
+	String::empty() : params["contactname"];
+    if (type == MARKUP_ROOMCHATPRIVATE && !nick)
+	return false;
+    buildChatFileName(buf,type,account,contact,nick);
     return true;
 }
 
 	const char* no = String::boolText(false);
 	NamedList p("");
 	p.addParam("show:archive_frame_search",no);
-	p.addParam("property:" + s_logList + ":_yate_flatlist",String::boolText(true));
 	Client::self()->setParams(&p,w);
     }
     return false;
 void CALogic::exitingClient()
 {
     Client::self()->setVisible(s_wndArch,false);
+    // Clear data now: close sessions
+    s_chatArchive.clear(true);
     // Stop workers
     searchStop();
     refreshStop();
     if (act.startSkip(s_archPrefix,false)) {
 	// Chat log actions nedding parameters
 	if (params) {
-	    if (params->getBoolValue("muc")) {
-		Debug(DebugStub,"Client archive: MUC not implemented");
-		return false;
-	    }
 	    if (act == s_actionLogChat)
 		return s_chatArchive.logChat(*params);
 	    if (act == s_actionCloseChat)
 	ChatFile* f = static_cast<ChatFile*>(o->get());
 	Lock lock(f);
 	f->loadSessions();
-	NamedList* upd = new NamedList(f->toString());
-	upd->addParam("contact",f->contactDisplayName());
-	upd->addParam("account",f->account());
-	String info(f->contact());
-	info << "\r\nAccount: " << f->account();
-	upd->addParam("property:toolTip",info);
+	NamedList* upd = chatFileUiParams(f);
+	// Check if the room is already displayed. Create it if not found
+	if (f->type() == MARKUP_ROOMCHATPRIVATE && f->roomId() &&
+	    !(p.getParam(f->roomId()) ||
+	    Client::self()->getTableRow(s_logList,f->roomId(),0,w))) {
+	    NamedList* upd2 = 0;
+	    ChatFile* parent = s_chatArchive.getChatFile(f->roomId());
+	    if (parent)
+		upd2 = chatFileUiParams(parent);
+	    else {
+		upd2 = new NamedList("");
+		upd2->addParam("item_type",uiItemType(MARKUP_ROOMCHAT));
+		upd2->addParam("account",f->account());
+		upd2->addParam("contact",f->contact());
+		upd2->addParam("name",f->contact());
+	    }
+	    p.addParam(new NamedPointer(f->roomId(),upd2,String::boolText(true)));
+	    TelEngine::destruct(parent);
+	}
 	p.addParam(new NamedPointer(f->toString(),upd,String::boolText(true)));
 	count--;
 	if (!count) {
 	Client::self()->clearTable(s_sessList,w);
 	Client::self()->clearTable(s_sessHistory,w);
     }
-    s_chatArchive.clear();
+    s_chatArchive.clear(false);
     return true;
 }
 

share/skins/default/arch_contact_item.ui

     <number>0</number>
    </property>
    <item>
-    <widget class="QLabel" name="contact" >
+    <widget class="QLabel" name="name" >
      <property name="sizePolicy" >
       <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
        <horstretch>0</horstretch>

share/skins/default/arch_room_item.ui

+<ui version="4.0" >
+ <class>arch_room_item</class>
+ <widget class="QWidget" name="arch_room_item" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>471</width>
+    <height>22</height>
+   </rect>
+  </property>
+  <property name="sizePolicy" >
+   <sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize" >
+   <size>
+    <width>0</width>
+    <height>22</height>
+   </size>
+  </property>
+  <property name="maximumSize" >
+   <size>
+    <width>16777215</width>
+    <height>22</height>
+   </size>
+  </property>
+  <property name="windowTitle" >
+   <string>Form</string>
+  </property>
+  <property name="styleSheet" >
+   <string/>
+  </property>
+  <layout class="QHBoxLayout" >
+   <property name="spacing" >
+    <number>0</number>
+   </property>
+   <property name="leftMargin" >
+    <number>0</number>
+   </property>
+   <property name="topMargin" >
+    <number>0</number>
+   </property>
+   <property name="rightMargin" >
+    <number>0</number>
+   </property>
+   <property name="bottomMargin" >
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="state" >
+     <property name="sizePolicy" >
+      <sizepolicy vsizetype="Expanding" hsizetype="Fixed" >
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize" >
+      <size>
+       <width>16</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="maximumSize" >
+      <size>
+       <width>16</width>
+       <height>16777215</height>
+      </size>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="name" >
+     <property name="sizePolicy" >
+      <sizepolicy vsizetype="Expanding" hsizetype="Minimum" >
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet" >
+      <string>QLabel {
+  font-size:12px;
+}</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="count" >
+     <property name="sizePolicy" >
+      <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet" >
+      <string>QLabel {
+  font-size:12px;
+}</string>
+     </property>
+     <property name="indent" >
+      <number>6</number>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

share/skins/default/arch_roompriv_item.ui

+<ui version="4.0" >
+ <class>arch_roompriv_item</class>
+ <widget class="QWidget" name="arch_roompriv_item" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>471</width>
+    <height>22</height>
+   </rect>
+  </property>
+  <property name="sizePolicy" >
+   <sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize" >
+   <size>
+    <width>0</width>
+    <height>22</height>
+   </size>
+  </property>
+  <property name="maximumSize" >
+   <size>
+    <width>16777215</width>
+    <height>22</height>
+   </size>
+  </property>
+  <property name="windowTitle" >
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" >
+   <property name="spacing" >
+    <number>0</number>
+   </property>
+   <property name="leftMargin" >
+    <number>32</number>
+   </property>
+   <property name="topMargin" >
+    <number>0</number>
+   </property>
+   <property name="rightMargin" >
+    <number>0</number>
+   </property>
+   <property name="bottomMargin" >
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="name" >
+     <property name="sizePolicy" >
+      <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="styleSheet" >
+      <string>QLabel {
+  font-size:12px;
+}</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

share/skins/default/arch_session_item.ui

        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
-     <property name="text" >
-      <string/>
+     <property name="textFormat" >
+      <enum>Qt::PlainText</enum>
      </property>
      <property name="alignment" >
       <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>

share/skins/default/archive.ui

        <string>archive_logs_list</string>
       </property>
       <property name="_yate_uiwidget_class" stdset="0" >
-       <string>ContactList</string>
+       <string>QtCustomTree</string>
       </property>
       <property name="_yate_uiwidget_params" stdset="0" >
        <stringlist>
-        <string>property:_yate_flatlist=true</string>
-        <string>property:_yate_showofflinecontacts=true</string>
-        <string>property:_yate_hideemptygroups=true</string>
+        <string>_yate_tree_additemtype=chat</string>
+        <string>_yate_tree_additemtype=roomchat</string>
+        <string>_yate_tree_additemtype=roomprivchat</string>
         <string>property:itemsExpandable=true</string>
-        <string>property:autoExpand=true</string>
+        <string>property:autoExpand=false</string>
         <string>property:styleSheet=QTreeWidget {border-image: none;font-size: 11px;background:white;border: 1px solid #97acbc;}</string>
-        <string>property:_yate_nogroup_caption=Not set</string>
-        <string>property:_yate_itemui=contact:arch_contact_item.ui</string>
-        <string>property:_yate_itemui=group:arch_account_item.ui</string>
-        <string>property:_yate_itemstyle=contact:QWidget#${name}{background:white;}</string>
-        <string>property:_yate_itemstyle=group:QWidget#${name}{background:lightgrey;}</string>
-        <string>property:_yate_itemselectedstyle=contact:QWidget#${name}{background:lightblue;}</string>
-        <string>property:_yate_itemselectedstyle=group:QWidget#${name}{background:lightgrey;}</string>
-        <string>property:_yate_itemstatewidget=group:state</string>
-        <string>property:_yate_itemexpandedimage=group:expanded.png</string>
-        <string>property:_yate_itemcollapsedimage=group:collapsed.png</string>
-        <string>groupcount=count</string>
+        <string>property:_yate_itemui=chat:arch_contact_item.ui</string>
+        <string>property:_yate_itemui=roomchat:arch_room_item.ui</string>
+        <string>property:_yate_itemui=roomprivchat:arch_roompriv_item.ui</string>
+        <string>property:_yate_itemstyle=chat:QWidget#${name}{background:white;}</string>
+        <string>property:_yate_itemstyle=roomchat:QWidget#${name}{background:white;}</string>
+        <string>property:_yate_itemstyle=roomprivchat:QWidget#${name}{background:white;}</string>
+        <string>property:_yate_itemselectedstyle=chat:QWidget#${name}{background:lightblue;}</string>
+        <string>property:_yate_itemselectedstyle=roomchat:QWidget#${name}{background:lightblue;}</string>
+        <string>property:_yate_itemselectedstyle=roomprivchat:QWidget#${name}{background:lightblue;}</string>
+        <string>property:_yate_itemtooltip=chat:&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">\np, li { white-space: pre-wrap; }\n&lt;/style>&lt;/head>&lt;body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;">&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;span style=" font-size:14pt; font-weight:600;">${name}&lt;/span>&lt;/p>&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${contact}&lt;/p>&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Account: ${account}&lt;/p>&lt;/body>&lt;/html></string>
+        <string>property:_yate_itemtooltip=roomchat:&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">\np, li { white-space: pre-wrap; }\n&lt;/style>&lt;/head>&lt;body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;">&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;span style=" font-size:14pt; font-weight:600;">${contact}&lt;/span>&lt;/p>&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Account: ${account}&lt;/p>&lt;/body>&lt;/html></string>
+        <string>property:_yate_itemtooltip=roomprivchat:&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">\np, li { white-space: pre-wrap; }\n&lt;/style>&lt;/head>&lt;body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;">&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;span style=" font-size:14pt; font-weight:600;">${name}&lt;/span>&lt;/p>&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${contact}&lt;/p>&lt;p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Account: ${account}&lt;/p>&lt;/body>&lt;/html></string>
+        <string>property:_yate_itemstatewidget=roomchat:state</string>
+        <string>property:_yate_itemexpandedimage=roomchat:minus.png</string>
+        <string>property:_yate_itemcollapsedimage=roomchat:plus.png</string>
+        <string>property:_yate_itemstatswidget=roomchat:count</string>
+        <string>property:_yate_itemstatstemplate=roomchat:(${count})</string>
        </stringlist>
       </property>
      </widget>
            <string>archive_session_list</string>
           </property>
           <property name="_yate_uiwidget_class" stdset="0" >
-           <string>ContactList</string>
+           <string>QtCustomTree</string>
           </property>
           <property name="_yate_uiwidget_params" stdset="0" >
            <stringlist>
-            <string>property:_yate_flatlist=true</string>
-            <string>property:_yate_showofflinecontacts=true</string>
-            <string>property:_yate_hideemptygroups=true</string>
             <string>property:itemsExpandable=true</string>
             <string>property:autoExpand=true</string>
             <string>property:styleSheet=QTreeWidget {border-image: none;font-size: 11px;background:white;border: 1px solid #97acbc;}</string>
-            <string>property:_yate_nogroup_caption=Not set</string>
-            <string>property:_yate_itemui=contact:arch_session_item.ui</string>
-            <string>property:_yate_itemstyle=contact:QWidget#${name}{background:white;}</string>
-            <string>property:_yate_itemselectedstyle=contact:QWidget#${name}{background:lightblue;}</string>
+            <string>property:_yate_itemui=default:arch_session_item.ui</string>
+            <string>property:_yate_itemstyle=default:QWidget#${name}{background:white;}</string>
+            <string>property:_yate_itemselectedstyle=default:QWidget#${name}{background:lightblue;}</string>
            </stringlist>
           </property>
          </widget>

share/skins/default/minus.png

Added
New image

share/skins/default/mucchat.ui

       </widget>
      </item>
      <item>
+      <widget class="QToolButton" name="room_showlog_btn" >
+       <property name="minimumSize" >
+        <size>
+         <width>30</width>
+         <height>30</height>
+        </size>
+       </property>
+       <property name="maximumSize" >
+        <size>
+         <width>30</width>
+         <height>30</height>
+        </size>
+       </property>
+       <property name="autoRaise" >
+        <bool>true</bool>
+       </property>
+       <property name="_yate_identity" stdset="0" >
+        <string>room_showlog</string>
+       </property>
+       <property name="_yate_setaction" stdset="0" >
+        <string>room_showlog</string>
+       </property>
+       <property name="_yate_noautoconnect" stdset="0" >
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
       <spacer>
        <property name="orientation" >
         <enum>Qt::Horizontal</enum>
     <string>room_member_chat</string>
    </property>
   </action>
+  <action name="room_member_showlog" >
+   <property name="icon" >
+    <iconset>archive.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Show log</string>
+   </property>
+   <property name="_yate_identity" stdset="0" >
+    <string>room_member_showlog</string>
+   </property>
+  </action>
+  <action name="room_showlog" >
+   <property name="icon" >
+    <iconset>archive.png</iconset>
+   </property>
+   <property name="text" >
+    <string>Show log</string>
+   </property>
+   <property name="_yate_identity" stdset="0" >
+    <string>room_showlog</string>
+   </property>
+  </action>
  </widget>
  <tabstops>
   <tabstop>message</tabstop>

share/skins/default/plus.png

Added
New image