/**
* 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.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
* <p>
*/
package org.olat.instantMessaging.ui;
import java.util.ArrayList;
import java.util.List;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.Window;
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.floatingresizabledialog.FloatingResizableDialogController;
import org.olat.core.gui.themes.Theme;
import org.olat.core.gui.util.SyntheticUserRequest;
import org.olat.core.id.OLATResourceable;
import org.olat.core.util.StringHelper;
import org.olat.core.util.event.EventBus;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.nodes.iq.AssessmentEvent;
import org.olat.ims.qti.process.AssessmentInstance;
import org.olat.instantMessaging.CloseInstantMessagingEvent;
import org.olat.instantMessaging.InstantMessageNotification;
import org.olat.instantMessaging.InstantMessagingEvent;
import org.olat.instantMessaging.InstantMessagingModule;
import org.olat.instantMessaging.InstantMessagingService;
import org.olat.instantMessaging.OpenInstantMessageEvent;
import org.olat.instantMessaging.model.Buddy;
import org.olat.instantMessaging.model.BuddyStats;
import org.olat.instantMessaging.model.Presence;
/**
* Description:<br />
* Main controller which initiates 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 {
private static final String ACTION_MSG = "cmd.msg";
private VelocityContainer main = createVelocityContainer("topnav");
private VelocityContainer chatContent = createVelocityContainer("chat");
//new messages
private List<Long> showNewMessageHolder = new ArrayList<Long>();
private VelocityContainer newMsgIcon = createVelocityContainer("newMsgIcon");
//roster
private Panel rosterPanel;
private Link onlineOfflineCount;
private IMBuddyListController rosterCtr;
private FloatingResizableDialogController rosterPanelCtr;
//status changes
private final Link available, unavailable, dnd;
//chat list
private JSAndCSSComponent jsc;
private ChatManagerController chatMgrCtrl;
private String imStatus;
private int stateUpdateCounter = 0;
private boolean inAssessment = false;
private EventBus singleUserEventCenter;
private final InstantMessagingService imService;
public InstantMessagingMainController(UserRequest ureq, WindowControl wControl) {
super(ureq, wControl);
imService = CoreSpringFactory.getImpl(InstantMessagingService.class);
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(), 5000);
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.mp3";
newMessageSoundURL = newMessageSoundURL.replace("/themes/" + guiTheme.getIdentifyer(), "/themes/light");
newMsgIcon.contextPut("newMessageSoundURL", newMessageSoundURL);
loadNotifications();
// status changer links
available = LinkFactory.createLink("presence.available", main, this);
available.setIconLeftCSS("o_icon o_icon_status_available o_icon-fw");
available.setUserObject(Presence.available.name());
dnd = LinkFactory.createLink("presence.dnd", main, this);
dnd.setIconLeftCSS("o_icon o_icon_status_dnd o_icon-fw");
dnd.setUserObject(Presence.dnd.name());
unavailable = LinkFactory.createLink("presence.unavailable", main, this);
unavailable.setIconLeftCSS("o_icon o_icon_status_unavailable o_icon-fw");
unavailable.setUserObject(Presence.unavailable.name());
updateStatusCss(null);
// roster launcher (offline / online) link
InstantMessagingModule imModule = CoreSpringFactory.getImpl(InstantMessagingModule.class);
if (imModule.isGroupPeersEnabled()) {
onlineOfflineCount = LinkFactory.createCustomLink("onlineOfflineCount", "cmd.roster", "", Link.NONTRANSLATED, main, this);
onlineOfflineCount.setTooltip(translate("im.roster.intro"));
onlineOfflineCount.registerForMousePositionEvent(true);
onlineOfflineCount.setCustomEnabledLinkCSS("badge");
updateBuddyStats();
main.put("buddiesSummaryPanel", onlineOfflineCount);
getWindowControl().getWindowBackOffice().addCycleListener(this);
}
main.put("newMsgPanel", newMsgIcon);
rosterPanel = new Panel("rosterPanel");
main.put("rosterPanel", rosterPanel);
//creates and manages the p2p chats
chatMgrCtrl = new ChatManagerController(ureq, wControl);
listenTo(chatMgrCtrl);
newMsgIcon.put("chats", chatMgrCtrl.getInitialComponent());
//listen to private chat messages
imService.listenChat(getIdentity(), getPrivatListenToResourceable(), null, false, false, this);
singleUserEventCenter = ureq.getUserSession().getSingleUserEventCenter();
singleUserEventCenter.registerFor(this, getIdentity(), InstantMessagingService.ASSESSMENT_EVENT_ORES);
singleUserEventCenter.registerFor(this, getIdentity(), InstantMessagingService.TOWER_EVENT_ORES);
putInitialPanel(main);
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
*/
@Override
protected void doDispose() {
imService.unlistenChat(getIdentity(), getPrivatListenToResourceable(), this);
singleUserEventCenter.deregisterFor(this, InstantMessagingService.ASSESSMENT_EVENT_ORES);
singleUserEventCenter.deregisterFor(this, InstantMessagingService.TOWER_EVENT_ORES);
getWindowControl().getWindowBackOffice().removeCycleListener(this);
}
public OLATResourceable getPrivatListenToResourceable() {
return OresHelper.createOLATResourceableInstance("Buddy", getIdentity().getKey());
}
/**
* @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 (source == available || source == dnd || source == unavailable) {
Link link = (Link) source;
doChangeStatus((String)link.getUserObject());
} else if (source == onlineOfflineCount) {
doOpenRoster(ureq);
} else if (source instanceof Link) {
Link link = (Link)source;
//chat gets created by click on buddy list
if (link.getCommand().equals(ACTION_MSG)) {
//chats gets created by click on new message icon
Object obj = link.getUserObject();
if(obj instanceof Buddy) {
Buddy buddy = (Buddy)obj;
chatMgrCtrl.createChat(ureq, buddy);
showNewMessageHolder.remove(buddy.getIdentityKey());
}
newMsgIcon.setDirty(true);
}
}
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if (source == rosterPanelCtr) {
//closing the floating panel event
updateBuddyStats();
removeAsListenerAndDispose(rosterCtr);
removeAsListenerAndDispose(rosterPanelCtr);
rosterCtr = null;
rosterPanelCtr = null;
rosterPanel.setContent(null);
} else if (source == rosterCtr) {
if(event instanceof OpenInstantMessageEvent) {
OpenInstantMessageEvent e = (OpenInstantMessageEvent)event;
doOpenPrivateChat(ureq, e.getBuddy());
}
} else if (source == chatMgrCtrl) {
//closing events from chat manager controller
}
}
/**
* gets called if either a new message or a presence change from one of the buddies happens
* or an Assessment starts or ends
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
public void event(Event event) {
if(event instanceof InstantMessagingEvent) {
processInstantMessageEvent((InstantMessagingEvent)event);
} else if (event instanceof AssessmentEvent) {
processAssessmentEvent((AssessmentEvent)event);
} else if (event instanceof OpenInstantMessageEvent) {
processOpenInstantMessageEvent((OpenInstantMessageEvent)event);
} else if(event instanceof CloseInstantMessagingEvent) {
processCloseInstantMessageEvent((CloseInstantMessagingEvent)event);
} else if(Window.BEFORE_INLINE_RENDERING.equals(event)) {
if(++stateUpdateCounter % 25 == 0) {
updateBuddyStats();
}
}
}
private void updateBuddyStats() {
if(onlineOfflineCount != null) {
BuddyStats stats = imService.getBuddyStats(getIdentity());
onlineOfflineCount.setCustomDisplayText(translate("im.roster.launch", new String[]{stats.getOnlineBuddies() + "", stats.getOfflineBuddies() + ""}));
}
}
private void loadNotifications() {
List<InstantMessageNotification> notifications = imService.getNotifications(getIdentity());
for(InstantMessageNotification notification:notifications) {
if(!showNewMessageHolder.contains(notification.getFromIdentityKey())) {
showNewMessageHolder.add(notification.getFromIdentityKey());
Buddy buddy = imService.getBuddyById(notification.getFromIdentityKey());
createShowNewMessageLink(buddy);
}
}
}
private void doOpenPrivateChat(UserRequest ureq, Buddy buddy) {
//info.getInitialMessages()
chatMgrCtrl.createChat(ureq, buddy);
}
private void doOpenRoster(UserRequest ureq) {
removeAsListenerAndDispose(rosterCtr);
removeAsListenerAndDispose(rosterPanelCtr);
rosterCtr = new IMBuddyListController(ureq, getWindowControl());
listenTo(rosterCtr);
rosterPanelCtr = new FloatingResizableDialogController(ureq, getWindowControl(), rosterCtr.getInitialComponent(),
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);
}
private void doChangeStatus(String status) {
imService.updateStatus(getIdentity(), status);
updateStatusCss(status);
}
private void updateStatusCss(String status) {
if(!StringHelper.containsNonWhitespace(status)) {
imStatus = imService.getStatus(getIdentity().getKey());
} else {
imStatus = status;
}
if(imStatus == null) {
imStatus = Presence.available.name();
imService.updateStatus(getIdentity(), imStatus);
}
String cssClass = "o_icon o_icon_status_" + imStatus;
main.contextPut("statusClass", cssClass);
}
private void processAssessmentEvent(AssessmentEvent event) {
if(event.getEventType().equals(AssessmentEvent.TYPE.STARTED)) {
inAssessment = true;
main.contextPut("inAssessment", true);
chatMgrCtrl.closeAllChats();
if(rosterPanelCtr != null) {
rosterPanelCtr.executeCloseCommand();
}
} else if(event.getEventType().equals(AssessmentEvent.TYPE.STOPPED)) {
OLATResourceable a = OresHelper.createOLATResourceableType(AssessmentInstance.class);
if (singleUserEventCenter.getListeningIdentityCntFor(a) < 1) {
inAssessment = false;
main.contextPut("inAssessment", false);
loadNotifications();
}
}
}
private void processOpenInstantMessageEvent(OpenInstantMessageEvent event) {
UserRequest ureq = event.getUserRequest();
if(ureq != null) {
if(event.getBuddy() != null) {
chatMgrCtrl.createChat(ureq, event.getBuddy());
} else if(event.getOres() != null) {
//open a group/course chat
chatMgrCtrl.createGroupChat(ureq, event.getOres(), event.getRoomName(), event.isVip());
}
}
}
private void processCloseInstantMessageEvent(CloseInstantMessagingEvent event) {
if(event.getOres() == null) {
close();
} else {
closeChat(event.getOres());
}
}
private void closeChat(OLATResourceable ores) {
if(chatMgrCtrl != null) {
chatMgrCtrl.closeChat(ores);
}
}
private void close() {
if(rosterPanelCtr != null) {
rosterPanelCtr.executeCloseCommand();
removeAsListenerAndDispose(rosterPanelCtr);
rosterPanel.setContent(null);
}
if(chatMgrCtrl != null) {
chatMgrCtrl.closeAllChats();
}
}
private void processInstantMessageEvent(InstantMessagingEvent imEvent) {
if (imEvent.getCommand().equals("message")) {
//user receives messages from an other user
Long fromId = imEvent.getFromId();
if(!chatMgrCtrl.hasRunningChat(imEvent.getChatResource())) {
//only show icon if no chat running or msg from other user
//add follow up message to info holder
if (!showNewMessageHolder.contains(fromId)) {
Buddy buddy = imService.getBuddyById(fromId);
if(Presence.available.name().equals(imStatus) && !inAssessment) {
doOpenPrivateChat(new SyntheticUserRequest(getIdentity(), getLocale()), buddy);
} else {
showNewMessageHolder.add(fromId);
createShowNewMessageLink(buddy);
}
}
}
}
}
/**
* creates an new message icon link
* @param jabberId
*/
private Link createShowNewMessageLink(Buddy buddy) {
Link link = LinkFactory.createCustomLink(buddy.getIdentityKey().toString(), ACTION_MSG, "", Link.NONTRANSLATED, newMsgIcon, this);
link.registerForMousePositionEvent(true);
link.setIconLeftCSS("o_icon o_icon_message o_icon-lg");
String buddyName = StringHelper.escapeHtml(buddy.getName());
link.setTooltip(translate("im.new.message", new String[]{ buddyName }));
link.setUserObject(buddy);
newMsgIcon.put(buddy.getIdentityKey().toString(), link);
return link;
}
}