/**
* OLAT - Online Learning and Training<br />
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br />
* you may not use this file except in compliance with the License.<br />
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br />
* software distributed under the License is distributed on an "AS IS" BASIS, <br />
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br />
* See the License for the specific language governing permissions and <br />
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br />
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.instantMessaging.rosterandchat;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.Panel;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.control.creator.ControllerCreator;
import org.olat.core.gui.control.floatingresizabledialog.FloatingResizableDialogController;
import org.olat.core.gui.control.generic.popup.PopupBrowserWindow;
import org.olat.core.gui.themes.Theme;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.event.GenericEventListener;
import org.olat.instantMessaging.ClientHelper;
import org.olat.instantMessaging.ClientManager;
import org.olat.instantMessaging.ConncectedUsersHelper;
import org.olat.instantMessaging.InstantMessagingEvent;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.instantMessaging.ui.ConnectedClientsListController;
/**
* Description:<br />
* Main controller which initates the connection and provides status change/roster and chat possibilities
*
* <P>
* Initial Date: 26.04.2007 <br />
* @author guido
*/
public class InstantMessagingMainController extends BasicController implements GenericEventListener {
public static final String ACTION_CHAT = "cmd.chat";
public static final String ACTION_MSG = "cmd.msg";
private VelocityContainer main = createVelocityContainer("index");
private VelocityContainer chatContent = createVelocityContainer("chat");
private VelocityContainer chatMsgFieldContent = createVelocityContainer("chatMsgField");
private VelocityContainer statusChangerContent = createVelocityContainer("statusChangerContent");
private VelocityContainer statusChangerLink = createVelocityContainer("statusChangerLink");
private VelocityContainer newMsgIcon = createVelocityContainer("newMsgIcon");
private VelocityContainer buddiesSummary = createVelocityContainer("buddiesSummary");
private VelocityContainer buddiesList = createVelocityContainer("buddiesList");
private VelocityContainer buddiesListContent = createVelocityContainer("buddiesListContent");
private Panel notifieNewMsgPanel;
private Panel buddiesSummaryPanel, statusPanel;
private Panel statusChangerPanel, rosterPanel;
private Link available, unavailable, dnd, xa, away;
private Link chat, onlineOfflineCount;
private Link toggleOffline, toggleGroups, showOtherUsers, statusChanger;
private int toggleOfflineMode;
private int toggleGroupsMode;
private ClientHelper clientHelper;
private Panel buddiesListContentPanel;
private ClientManager clientManager;
private String username;
private FloatingResizableDialogController statusChangerPanelCtr;
private FloatingResizableDialogController rosterPanelCtr;
private FloatingResizableDialogController chatPanelCtr;
private JSAndCSSComponent jsc;
private ChatManagerController chatMgrCtrl;
private Map<String, NewMessageIconInfo> showNewMessageHolder = new HashMap<String, NewMessageIconInfo>(2);
public InstantMessagingMainController(UserRequest ureq, WindowControl wControl) {
super(ureq, wControl);
username = getIdentity().getName();
clientHelper = new ClientHelper(username, this, buddiesListContent, getTranslator());
this.clientManager = InstantMessagingModule.getAdapter().getClientManager();
clientManager.registerEventListener(username, this, true);
boolean ajaxOn = getWindowControl().getWindowBackOffice().getWindowManager().isAjaxEnabled();
chatContent.contextPut("isAjaxMode", Boolean.valueOf(ajaxOn));
// checks with the given intervall if dirty components are available to rerender
jsc = new JSAndCSSComponent("intervall", this.getClass(), null, null, false, null, InstantMessagingModule.getIDLE_POLLTIME());
main.put("updatecontrol", jsc);
// configure new message sound
newMsgIcon.contextPut("iconsHolder", showNewMessageHolder);
Theme guiTheme = getWindowControl().getWindowBackOffice().getWindow().getGuiTheme();
String newMessageSoundURL = guiTheme.getBaseURI() + "/sounds/new_message.wav";
File soundFile = new File(WebappHelper.getContextRoot() + "/themes/" + guiTheme.getIdentifyer() + "/sounds/new_message.wav");
if (!soundFile.exists()) {
// fallback to default theme when file does not exist in configured theme
newMessageSoundURL = newMessageSoundURL.replace("/themes/" + guiTheme.getIdentifyer(), "/themes/default");
}
newMsgIcon.contextPut("newMessageSoundURL", newMessageSoundURL);
notifieNewMsgPanel = new Panel("newMsgPanel");
notifieNewMsgPanel.setContent(newMsgIcon);
statusChanger = LinkFactory.createCustomLink("statusChanger", "cmd.status", "", Link.NONTRANSLATED, statusChangerLink, this);
statusChanger.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_"+clientManager.getInstantMessagingClient(username).getDefaultRosterStatus()+"_icon");
statusChanger.registerForMousePositionEvent(true);
statusChanger.setTooltip(getTranslator().translate("im.status.change.long"), false);
statusChangerPanel = new Panel("statusChangerPanel");
statusChangerPanel.setContent(statusChanger);
statusPanel = new Panel("statusPanel");
//set defaults
buddiesSummaryPanel = new Panel("buddiesSummaryPanel");
buddiesSummaryPanel.setContent(buddiesSummary);
rosterPanel = new Panel("rosterPanel");
onlineOfflineCount = LinkFactory.createCustomLink("onlineOfflineCount", "cmd.roster", "", Link.NONTRANSLATED, buddiesSummary, this);
onlineOfflineCount.setCustomDisplayText("(0/0)");
onlineOfflineCount.setTooltip(getTranslator().translate("im.roster.intro"), false);
onlineOfflineCount.registerForMousePositionEvent(true);
/**
* status changer links
*/
available = LinkFactory.createLink("presence.available", statusChangerContent, this);
available.setCustomEnabledLinkCSS("o_instantmessaging_available_icon");
chat = LinkFactory.createLink("presence.chat", statusChangerContent, this);
chat.setCustomEnabledLinkCSS("o_instantmessaging_chat_icon");
away = LinkFactory.createLink("presence.away", statusChangerContent, this);
away.setCustomEnabledLinkCSS("o_instantmessaging_away_icon");
xa = LinkFactory.createLink("presence.xa", statusChangerContent, this);
xa.setCustomEnabledLinkCSS("o_instantmessaging_xa_icon");
dnd = LinkFactory.createLink("presence.dnd", statusChangerContent, this);
dnd.setCustomEnabledLinkCSS("o_instantmessaging_dnd_icon");
unavailable = LinkFactory.createLink("presence.unavailable", statusChangerContent, this);
unavailable.setCustomEnabledLinkCSS("o_instantmessaging_unavailable_icon");
statusChangerContent.contextPut("contextpath", WebappHelper.getServletContextPath());
statusChangerContent.contextPut("lang", ureq.getLocale().toString());
/**
* buddies list links
*/
toggleOffline = LinkFactory.createCustomLink("toggleOffline", "cmd.offline", "", Link.NONTRANSLATED, buddiesList, this);
toggleOffline.setCustomDisplayText(getTranslator().translate("im.show.offline.buddies"));
toggleOffline.setCustomEnabledLinkCSS("o_instantmessaging_showofflineswitch");
toggleGroups = LinkFactory.createCustomLink("toggleGroups", "cmd.groups", "", Link.NONTRANSLATED, buddiesList, this);
toggleGroups.setCustomDisplayText(getTranslator().translate("im.show.groups"));
toggleGroups.setCustomEnabledLinkCSS("o_instantmessaging_showgroupswitch");
showOtherUsers = LinkFactory.createLink("im.others.connected", buddiesList, this);
showOtherUsers.setCustomEnabledLinkCSS("o_instantmessaging_footerlinks");
showOtherUsers.setAjaxEnabled(false); //opens new window -> disable background post!
showOtherUsers.setTarget("_blank");
buddiesList.contextPut("othersConnected", new ConncectedUsersHelper());
buddiesList.contextPut("contextpath", WebappHelper.getServletContextPath());
buddiesList.contextPut("lang", ureq.getLocale().toString());
buddiesList.contextPut("othersConnected", new ConncectedUsersHelper());
buddiesListContent.contextPut("imclient", clientHelper);
buddiesListContentPanel = new Panel("buddiesListContent");
buddiesListContentPanel.setContent(buddiesListContent);
buddiesList.put("buddiesListContent", buddiesListContentPanel);
main.put("newMsgPanel", notifieNewMsgPanel);
main.put("statusChangerPanel", statusChangerPanel);
main.put("buddiesSummaryPanel", buddiesSummaryPanel);
main.put("rosterPanel", rosterPanel);
main.put("statusPanel", statusPanel);
//creates and manages the p2p chats
chatMgrCtrl = new ChatManagerController(ureq, wControl);
listenTo(chatMgrCtrl);
newMsgIcon.put("chats", chatMgrCtrl.getInitialComponent());
putInitialPanel(main);
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
*/
@Override
protected void doDispose() {
InstantMessagingModule.getAdapter().getClientManager().deregisterControllerListener(username, this);
InstantMessagingModule.getAdapter().getClientManager().destroyInstantMessagingClient(username);
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
*/
@Override
public void event(UserRequest ureq, Component source, Event event) {
if (clientHelper.isConnected()) {
boolean ajaxOn = getWindowControl().getWindowBackOffice().getWindowManager().isAjaxEnabled();
chatContent.contextPut("isAjaxMode", Boolean.valueOf(ajaxOn));
if (source == statusChanger) {
if(statusChangerPanelCtr != null) {
removeAsListenerAndDispose(statusChangerPanelCtr);
}
statusChangerPanelCtr = new FloatingResizableDialogController(ureq, getWindowControl(), statusChangerContent,
getTranslator().translate("im.status.change"), 210, 200, statusChanger.getOffsetX()-130, statusChanger.getOffsetY()+25, null, null, false, false, true, "im_status");
listenTo(statusChangerPanelCtr);
statusPanel.setContent(statusChangerPanelCtr.getInitialComponent());
statusChanger.setDirty(false);
} else if (source == onlineOfflineCount) {
if (!clientHelper.isChatDisabled()) { //only open panel when chat is not disabled
if(rosterPanelCtr != null) {
removeAsListenerAndDispose(rosterPanelCtr);
}
rosterPanelCtr = new FloatingResizableDialogController(ureq, getWindowControl(), buddiesList,
getTranslator().translate("im.buddies"), 300, 500, onlineOfflineCount.getOffsetX()-80, onlineOfflineCount.getOffsetY()+25, null, null, true, true, true, "im_roster");
listenTo(rosterPanelCtr);
rosterPanel.setContent(rosterPanelCtr.getInitialComponent());
onlineOfflineCount.setDirty(false);
}
} else if (source == available) {
clientHelper.sendPresenceAvailable(Presence.Mode.available);
changeCSS();
} else if (source == chat) {
clientHelper.sendPresenceAvailable(Presence.Mode.chat);
changeCSS();
} else if (source == away) {
clientHelper.sendPresenceAvailable(Presence.Mode.away);
changeCSS();
} else if (source == xa) {
clientHelper.sendPresenceAvailable(Presence.Mode.xa);
changeCSS();
} else if (source == dnd) {
clientHelper.sendPresenceAvailable(Presence.Mode.dnd);
changeCSS();
} else if (source == unavailable) {
clientHelper.sendPresenceUnavailable();
changeCSS();
}
//buddies list
else if (source == toggleOffline) {
if (toggleOfflineMode == 0) {
toggleOffline.setCustomDisplayText(getTranslator().translate("im.hide.offline.buddies"));
toggleOfflineMode = 1;
clientHelper.setShowOfflineBuddies(true);
} else {
toggleOffline.setCustomDisplayText(getTranslator().translate("im.show.offline.buddies"));
toggleOfflineMode = 0;
clientHelper.setShowOfflineBuddies(false);
}
buddiesListContent.setDirty(true);
} else if (source == toggleGroups) {
if (toggleGroupsMode == 0) {
toggleGroups.setCustomDisplayText(getTranslator().translate("im.hide.groups"));
toggleGroupsMode = 1;
clientHelper.setShowGroupsInRoster(true);
} else {
toggleGroups.setCustomDisplayText(getTranslator().translate("im.show.groups"));
toggleGroupsMode = 0;
clientHelper.setShowGroupsInRoster(false);
}
buddiesListContent.setDirty(true);
} else if (source == showOtherUsers) {
//open new window with a list of online users
ControllerCreator ctrlCreator = new ControllerCreator() {
public Controller createController(UserRequest lureq, WindowControl lwControl) {
ConnectedClientsListController clientsListCtr = new ConnectedClientsListController(lureq, lwControl);
LayoutMain3ColsController mainLayoutCtr = new LayoutMain3ColsController(lureq, lwControl, null, null, clientsListCtr.getInitialComponent(), null);
mainLayoutCtr.addDisposableChildController(clientsListCtr);
return mainLayoutCtr;
}
};
//wrap the content controller into a full header layout
ControllerCreator layoutCtrlr = BaseFullWebappPopupLayoutFactory.createAuthMinimalPopupLayout(ureq, ctrlCreator);
//open in new browser window
PopupBrowserWindow pbw = getWindowControl().getWindowBackOffice().getWindowManager().createNewPopupBrowserWindowFor(ureq, layoutCtrlr);
pbw.open(ureq);
//
}
if (source instanceof Link) {
Link link = (Link)source;
//chat gets created by click on buddy list
if(link.getCommand().equals(ACTION_CHAT)) {
chatMgrCtrl.createChat(ureq, getWindowControl(), (String)link.getUserObject(), link.getOffsetX()-470, link.getOffsetY(), true, null);
} else if (link.getCommand().equals(ACTION_MSG)) {//chats gets created by click on new message icon
NewMessageIconInfo info = (NewMessageIconInfo)link.getUserObject();
Link msgLink = info.getNewMessageLink();
chatMgrCtrl.createChat(ureq, getWindowControl(), info.getJabberId(), msgLink.getOffsetX()-470, msgLink.getOffsetY(), true, info.getInitialMessages());
showNewMessageHolder.remove(info.getJabberId());
newMsgIcon.setDirty(true);
}
}
} else {
//connection is broken, set "service not available" to the GUI
onlineOfflineCount.setCustomDisplayText("n/a");
onlineOfflineCount.setTitle(getTranslator().translate("im.error.connection"));
statusChanger.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_error_icon");
}
}
private void changeCSS() {
statusChanger.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_"+clientHelper.getStatus()+"_icon");
getWindowControl().setInfo(getTranslator().translate("new.status")+" "+getTranslator().translate("presence."+clientHelper.getStatus()));
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if (source == statusChangerPanelCtr) {
//closing the floating panel event
statusChangerPanel.setContent(statusChangerLink);
//remove the status controller
removeAsListenerAndDispose(statusChangerPanelCtr);
statusChangerPanelCtr = null;
statusPanel.setContent(null);
statusChanger.setDirty(false);
} else if (source == rosterPanelCtr) {
//closing the floating panel event
buddiesSummaryPanel.setContent(onlineOfflineCount);
//refresh summary on close of roster
onlineOfflineCount.setCustomDisplayText(clientHelper.buddyCountOnline());
//remove the roster controller
removeAsListenerAndDispose(rosterPanelCtr);
rosterPanelCtr = null;
rosterPanel.setContent(null);
onlineOfflineCount.setDirty(false);
} else if (source == chatPanelCtr) {
//closing the floating panel event
notifieNewMsgPanel.setContent(newMsgIcon);
chatMsgFieldContent.contextPut("chatMessages", "");
//decrease polling time when not chatting
jsc.setRefreshIntervall(InstantMessagingModule.getIDLE_POLLTIME());
} else if (source == chatMgrCtrl) {
//closing events from chat manager controller
notifieNewMsgPanel.setContent(newMsgIcon);
}
}
/**
* gets called if either a new message or a presence change from one of the buddies happens
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
public void event(Event event) {
InstantMessagingEvent imEvent = (InstantMessagingEvent)event;
if (imEvent.getCommand().equals("presence")) {
Presence presence = (Presence) imEvent.getPacket();
if (isLogDebugEnabled()) logDebug("incoming presence for user: "+presence.getFrom() +" type: "+presence, null);
getWindowControl().getWindowBackOffice().invokeLater(new Runnable(){
public void run() {
//smack does not immediately update it's presence information, so when testing only with one person the OnlineOffline count value can be wrong
//in a productive environment there are normally lot's of presence packages
onlineOfflineCount.setCustomDisplayText(clientHelper.buddyCountOnline());
onlineOfflineCount.setDirty(true);
//adjust also presence icon in gui it may be auto changed by cronjob
statusChanger.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_"+clientHelper.getStatus()+"_icon");
statusChanger.setDirty(true);
}});
}
else if (imEvent.getCommand().equals("message")) {
//user receives messages from an other user
Message initialMessage = (Message)imEvent.getPacket();
String jid = extractJid(initialMessage.getFrom());
//recentChatPartners.add(initialMessage.getFrom());
if (((initialMessage.getType() == Message.Type.chat || initialMessage.getType() == Message.Type.normal)) && initialMessage.getBody() != null) {
if(!chatMgrCtrl.hasRunningChat(jid)) {//only show icon if no chat running or msg from other user
//add follow up message to info holder
if (showNewMessageHolder.get(jid) != null) {
NewMessageIconInfo info = showNewMessageHolder.get(extractJid(initialMessage.getFrom()));
info.addInitialMessage(initialMessage);
} else {
NewMessageIconInfo newMessageInfo = new NewMessageIconInfo(initialMessage.getFrom(), initialMessage);
showNewMessageHolder.put(jid, newMessageInfo);
newMessageInfo.setNewMessageLink(createShowNewMessageLink(initialMessage.getFrom(), newMessageInfo));
}
}
}
}
}
/**
* creates an new message icon link
* @param jabberId
*/
private Link createShowNewMessageLink(String jabberId, NewMessageIconInfo newMessageInfo) {
Link link = LinkFactory.createCustomLink(extractJid(jabberId), ACTION_MSG, "", Link.NONTRANSLATED, newMsgIcon, this);
link.registerForMousePositionEvent(true);
link.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_new_msg_icon");
link.setTooltip(getTranslator().translate("im.new.message", new String[]{jabberId}), false);
link.setUserObject(newMessageInfo);
return link;
}
/**
* extract the jabber id without the resource appendix
* @param jabberId
* @return
*/
private String extractJid(String jabberId) {
int pos = jabberId.lastIndexOf("/");
if (pos > 0) {
return jabberId.substring(0, jabberId.lastIndexOf("/"));
}
return jabberId;
}
}