/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.plugin.desktoputil.presence;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.resources.*;
import org.jitsi.util.*;
/**
* The <tt>AbstractStatusMessageMenu<tt> is added to every status selector box
* in order to enable the user to choose a status message.
*
* @author Yana Stamcheva
* @author Damian Minkov
*/
public abstract class AbstractStatusMessageMenu
implements ActionListener,
ItemListener,
PropertyChangeListener
{
/**
* Property used to fire property change that the status message
* has changed.
*/
public static final String STATUS_MESSAGE_UPDATED_PROP =
"STATUS_MESSAGE_UPDATED";
/**
* Property used to fire property change that the custom status messages
* have changed, a new one has been added or they are cleared.
*/
public static final String CUSTOM_STATUS_MESSAGES_UPDATED_PROP =
"CUSTOM_STATUS_MESSAGES_UPDATED";
/**
* The prefix used to store custom status messages.
*/
private final static String CUSTOM_MESSAGES_PREFIX =
"service.gui.CUSTOM_STATUS_MESSAGE";
/**
* The prefix to search for provisioned messages.
*/
private final static String PROVISIONED_MESSAGES_PREFIX =
"service.gui.PROVISIONED_STATUS_MESSAGE";
private static final String BRB_MESSAGE
= DesktopUtilActivator.getResources()
.getI18NString("service.gui.BRB_MESSAGE");
private static final String BUSY_MESSAGE
= DesktopUtilActivator.getResources()
.getI18NString("service.gui.BUSY_MESSAGE");
private Object noMessageItem;
private Object newMessageItem;
/**
* To clear and delete currently saved custom messages.
*/
private Object clearCustomMessageItem;
/**
* The pre-set busy message.
*/
private Object busyMessageItem;
/**
* The pre-set BRB message.
*/
private Object brbMessageItem;
/**
* The menu we will be populating.
*/
private Object menu;
/**
* All property change listeners registered so far.
* Static so we can communicate between status message menus.
*/
private static java.util.List<PropertyChangeListener>
propertyChangeListeners = new ArrayList<PropertyChangeListener>();
/**
* Creates an instance of <tt>AbstractStatusMessageMenu</tt>.
*
* @param swing should we use swing or awt
*/
public AbstractStatusMessageMenu(boolean swing)
{
ResourceManagementService R = DesktopUtilActivator.getResources();
String text = R.getI18NString("service.gui.SET_STATUS_MESSAGE");
if (swing)
{
JMenu menuInstance = new JMenu(text);
Icon icon = getMenuIcon();
if(icon != null)
menuInstance.setIcon(icon);
menu = menuInstance;
}
else
{
menu = new Menu(text);
}
noMessageItem = createMenuItem(
R.getI18NString("service.gui.NO_MESSAGE"));
newMessageItem = createMenuItem(
R.getI18NString("service.gui.NEW_MESSAGE"));
clearCustomMessageItem = createMenuItem(
R.getI18NString("service.gui.CLEAR_CUSTOM_MESSAGES"));
// check should we show the preset messages
if(ConfigurationUtils.isPresetStatusMessagesEnabled())
{
this.addSeparator();
busyMessageItem = createsCheckBoxMenuItem(BUSY_MESSAGE);
brbMessageItem = createsCheckBoxMenuItem(BRB_MESSAGE);
}
else
{
busyMessageItem = null;
brbMessageItem = null;
}
// load provisioned messages if any
loadProvisionedStatusMessages();
// load custom message
loadCustomStatusMessages();
addPropertyChangeListener(this);
}
/**
* Creates the appropriate menu item. Depending on the
* menu.
* @param text the menu item text.
* @return the item.
*/
private Object createMenuItem(String text)
{
if (menu instanceof JMenu)
{
JMenuItem menuItem = new JMenuItem(text);
menuItem.setName(text);
menuItem.addActionListener(this);
((JMenu) menu).add(menuItem);
return menuItem;
}
else
{
MenuItem menuItem = new MenuItem(text);
menuItem.setName(text);
menuItem.addActionListener(this);
((Menu) menu).add(menuItem);
return menuItem;
}
}
/**
* Creates the appropriate menu item. Depending on the
* menu.
* @param text the menu item text.
* @return the item.
*/
private Object createsCheckBoxMenuItem(String text)
{
if (menu instanceof JMenu)
{
JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(text);
menuItem.setName(text);
menuItem.addActionListener(this);
((JMenu) menu).add(menuItem);
return menuItem;
}
else
{
CheckboxMenuItem menuItem = new CheckboxMenuItem(text);
menuItem.setName(text);
menuItem.addItemListener(this);
((Menu) menu).add(menuItem);
return menuItem;
}
}
/**
* Creates a separator.
*/
private void addSeparator()
{
if (menu instanceof JMenu)
((JMenu) menu).addSeparator();
else
((Menu) menu).addSeparator();
}
/**
* Returns the menu used for status messages.
* When AbstractStatusMessageMenu created with swing==true this returns
* javax.swing.JMenu and if it is false it returns java.awt.Menu.
*
* @return the menu.
*/
public Object getMenu()
{
return menu;
}
/**
* Returns the menu components count.
* @return the menu components count.
*/
private int getMenuComponentCount()
{
if (menu instanceof JMenu)
return ((JMenu) menu).getMenuComponentCount();
else
return ((Menu) menu).getItemCount();
}
/**
* Returns array of menu components.
* @return array of menu components.
*/
private Object[] getMenuComponents()
{
if (menu instanceof JMenu)
return ((JMenu) menu).getMenuComponents();
else
{
int c = ((Menu) menu).getItemCount();
Object[] res = new Object[c];
for(int i = 0; i < c; i++)
{
res[i] = ((Menu) menu).getItem(i);
}
return res;
}
}
/**
* Returns the menu component on the specified index.
* @param index the index for the component.
* @return the menu component.
*/
private Object getMenuComponent(int index)
{
if (menu instanceof JMenu)
return ((JMenu) menu).getMenuComponent(index);
else
return ((Menu) menu).getItem(index);
}
/**
* Removes the menu component at the specified index.
* @param index of the component to remove.
*/
private void removeMenuComponent(int index)
{
if (menu instanceof JMenu)
((JMenu) menu).remove(index);
else
((Menu) menu).remove(index);
}
/**
* Removes the component and return the index it was using.
* @param item the item to remove.
* @return the index the item was placed before removing.
*/
private int removeMenuComponent(Object item)
{
if (menu instanceof JMenu)
{
int ix = ((JMenu) menu).getPopupMenu()
.getComponentIndex((JMenuItem)item);
((JMenu) menu).remove((JMenuItem)item);
return ix;
}
else
{
int ix = ((MenuItem)item).getAccessibleContext()
.getAccessibleIndexInParent();
((Menu) menu).remove((MenuItem)item);
return ix;
}
}
/**
* Action is performed on any of the items.
* @param e the event
*/
public void actionPerformed(ActionEvent e)
{
actionPerformed(e.getSource());
}
/**
* Returns the currently set status message.
* @return the currently set status message.
*/
public abstract String getCurrentStatusMessage();
/**
* Performs action on the selected menuItem.
* @param menuItem the selected menu item.
*/
public void actionPerformed(Object menuItem)
{
String statusMessage = "";
if (menuItem.equals(newMessageItem))
{
String currentStatusMessage = getCurrentStatusMessage();
NewStatusMessageDialog dialog = new NewStatusMessageDialog(
currentStatusMessage == null ?
"" : currentStatusMessage,
this);
dialog.setLocation(
Toolkit.getDefaultToolkit().getScreenSize().width/2
- dialog.getWidth()/2,
Toolkit.getDefaultToolkit().getScreenSize().height/2
- dialog.getHeight()/2
);
dialog.setVisible(true);
dialog.requestFocusInField();
// we will set status from the Status Message Dialog
return;
}
else if (menuItem.equals(clearCustomMessageItem))
{
removeAllCustomStatusMessages();
// and now let's delete the saved values
java.util.List<String> customMessagesProps =
DesktopUtilActivator.getConfigurationService()
.getPropertyNamesByPrefix(CUSTOM_MESSAGES_PREFIX, false);
for(String p : customMessagesProps)
{
DesktopUtilActivator.getConfigurationService()
.removeProperty(p);
}
// fire that a change has occur
fireCustomStatusMessagesUpdated();
}
else if (menuItem.equals(busyMessageItem))
{
statusMessage = BUSY_MESSAGE;
}
else if (menuItem.equals(brbMessageItem))
{
statusMessage = BRB_MESSAGE;
}
else if (menuItem instanceof CustomMessageItems)
{
statusMessage = ((CustomMessageItems)menuItem).getName();
}
else if (menuItem instanceof ProvisionedMessageItems)
{
statusMessage = ((ProvisionedMessageItems)menuItem).getName();
}
// we cannot get here after clicking 'new message'
publishStatusMessage(statusMessage, menuItem, false);
setCurrentMessage(statusMessage, menuItem, false);
}
/**
* Action on any of the CheckboxItem.
* @param e the event.
*/
public void itemStateChanged(ItemEvent e)
{
actionPerformed(e.getSource());
}
/**
* Will remove and clear all custom status messages.
*/
private void removeAllCustomStatusMessages()
{
// lets remove, we need the latest index removed so we can
// remove and the separator that is before it
int lastIx = -1;
for(Object c : getMenuComponents())
{
if(c instanceof CustomMessageItems)
{
lastIx = removeMenuComponent(c);
}
}
// remove the separator
if(lastIx - 1 >= 0 )
removeMenuComponent(lastIx - 1);
}
/**
* Publishes the new message in separate thread. If successfully ended
* the message item is created and added to te list of current status
* messages and if needed the message is saved.
* @param message the message to save
* @param menuItem the item which was clicked to set this status
* @param saveIfNewMessage whether to save the status on the custom
* statuses list.
*/
public abstract void publishStatusMessage(
String message,
Object menuItem,
boolean saveIfNewMessage);
/**
* Returns the button for new messages.
* @return the button for new messages.
*/
Object getNewMessageItem()
{
return newMessageItem;
}
/**
* Changes current message text in its item.
* @param message
* @param menuItem the menu item that was clicked
* @param saveNewMessage whether to save the newly created message
*/
protected void setCurrentMessage(final String message,
final Object menuItem,
final boolean saveNewMessage)
{
if(menuItem == null)
return;
/// we will be working with swing components, make sure it is in EDT
if(!SwingUtilities.isEventDispatchThread())
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
setCurrentMessage(message, menuItem, saveNewMessage);
}
});
return;
}
String oldMesage = getCurrentMessage();
// if message is null we cleared the status message
if(StringUtils.isNullOrEmpty(message))
{
clearSelectedItems();
fireStatusMessageUpdated(oldMesage, null);
return;
}
// a ne message was created
if(menuItem.equals(newMessageItem))
{
clearSelectedItems();
int ix = getLastCustomMessageIndex();
if(ix == -1)
{
this.addSeparator();
createsCustomMessageItem(message, -1, true);
}
else
{
createsCustomMessageItem(message, ix + 1, true);
}
if(saveNewMessage)
{
// lets save it
int saveIx = getLastCustomStatusMessageIndex();
DesktopUtilActivator.getConfigurationService().setProperty(
CUSTOM_MESSAGES_PREFIX + "." + String.valueOf(saveIx + 1),
message
);
}
// fire that we have added a new message
fireCustomStatusMessagesUpdated();
fireStatusMessageUpdated(oldMesage, message);
}
else if(menuItem instanceof JCheckBoxMenuItem
|| menuItem instanceof CheckboxMenuItem)
{
clearSelectedItems();
selectMenuItem(menuItem, oldMesage);
}
}
/**
* Searches for custom messages in configuration service and
* gets the highest index.
* @return the highest index of custom status messages.
*/
private int getLastCustomStatusMessageIndex()
{
int ix = -1;
java.util.List<String> customMessagesProps =
DesktopUtilActivator.getConfigurationService()
.getPropertyNamesByPrefix(CUSTOM_MESSAGES_PREFIX, false);
int prefixLen = CUSTOM_MESSAGES_PREFIX.length() + 1;
for(String p : customMessagesProps)
{
if(prefixLen > p.length())
continue;
String s = p.substring(prefixLen);
try
{
int i = Integer.parseInt(s);
if(i > ix)
ix = i;
}
catch(Throwable t)
{}
}
return ix;
}
/**
* Loads the previously saved custom status messages.
*/
private void loadCustomStatusMessages()
{
java.util.List<String> customMessagesProps =
DesktopUtilActivator.getConfigurationService()
.getPropertyNamesByPrefix(CUSTOM_MESSAGES_PREFIX, false);
if(customMessagesProps.size() > 0)
{
this.addSeparator();
}
for(String p : customMessagesProps)
{
String message =
DesktopUtilActivator.getConfigurationService().getString(p);
createsCustomMessageItem(message, -1, false);
}
}
/**
* Loads the provisioned status messages.
*/
private void loadProvisionedStatusMessages()
{
java.util.List<String> provMessagesProps =
DesktopUtilActivator.getConfigurationService()
.getPropertyNamesByPrefix(PROVISIONED_MESSAGES_PREFIX, false);
if(provMessagesProps.size() > 0)
{
this.addSeparator();
}
for(String p : provMessagesProps)
{
String message =
DesktopUtilActivator.getConfigurationService().getString(p);
createsProvisionedMessageItem(message);
}
}
/**
* Clears all items that they are not selected and its name is not bold.
*/
public void clearSelectedItems()
{
for(int i = 0; i < getMenuComponentCount(); i++)
{
Object c = getMenuComponent(i);
if(c instanceof JCheckBoxMenuItem)
{
JCheckBoxMenuItem checkItem =
(JCheckBoxMenuItem)c;
checkItem.setSelected(false);
checkItem.setText(checkItem.getName());
}
else if(c instanceof CheckboxMenuItem)
{
CheckboxMenuItem checkItem =
(CheckboxMenuItem)c;
checkItem.setState(false);
}
}
}
/**
* Checks for the index of the last component of CustomMessageItems class.
* @return the last component index of CustomMessageItems.
*/
private int getLastCustomMessageIndex()
{
int ix = -1;
for(int i = 0; i < getMenuComponentCount(); i++)
{
Object c = getMenuComponent(i);
if(c instanceof CustomMessageItems)
{
if(i > ix)
ix = i;
}
}
return ix;
}
public String getCurrentMessage()
{
for(int i = 0; i < getMenuComponentCount(); i++)
{
Object c = getMenuComponent(i);
if(c instanceof JCheckBoxMenuItem
&& ((JCheckBoxMenuItem) c).isSelected())
{
return ((JCheckBoxMenuItem) c).getName();
}
else if(c instanceof CheckboxMenuItem
&& ((CheckboxMenuItem) c).getState())
{
return ((CheckboxMenuItem) c).getName();
}
}
return null;
}
/**
* Add a PropertyChangeListener to the listener list.
* The listener is registered for all properties.
*
* @param listener The PropertyChangeChangeListener to be added
*/
public void addPropertyChangeListener(
PropertyChangeListener listener)
{
synchronized(propertyChangeListeners)
{
if(!propertyChangeListeners.contains(listener))
propertyChangeListeners.add(listener);
}
}
/**
* Remove a PropertyChangeListener from the listener list.
* This removes a ConfigurationChangeListener that was registered
* for all properties.
*
* @param listener The PropertyChangeListener to be removed
*/
public void removePropertyChangeListener(
PropertyChangeListener listener)
{
synchronized(propertyChangeListeners)
{
propertyChangeListeners.remove(listener);
}
}
/**
* Returns the descriptor common for this status message menu instance.
* @return the descriptor common for this status message menu instance.
*/
public abstract Object getDescriptor();
/**
* Fires that the status message has changed.
* @param oldMessage the old message
* @param newMessage the new message
*/
protected void fireStatusMessageUpdated(
String oldMessage, String newMessage)
{
PropertyChangeEvent evt =
new PropertyChangeEvent(
getDescriptor(),
STATUS_MESSAGE_UPDATED_PROP,
oldMessage, newMessage);
java.util.List<PropertyChangeListener> listeners;
synchronized(propertyChangeListeners)
{
listeners = new ArrayList<PropertyChangeListener>(
propertyChangeListeners);
}
for (PropertyChangeListener target : listeners)
target.propertyChange(evt);
}
/**
* Fires that the custom status messages have changed.
*/
private void fireCustomStatusMessagesUpdated()
{
PropertyChangeEvent evt =
new PropertyChangeEvent(
getDescriptor(),
CUSTOM_STATUS_MESSAGES_UPDATED_PROP,
null, null);
java.util.List<PropertyChangeListener> listeners;
synchronized(propertyChangeListeners)
{
listeners = new ArrayList<PropertyChangeListener>(
propertyChangeListeners);
}
for (PropertyChangeListener target : listeners)
target.propertyChange(evt);
}
/**
* Creates the appropriate menu item. Depending on the
* menu.
* @param text the menu item text.
* @return the item.
*/
private Object createsProvisionedMessageItem(String text)
{
if (menu instanceof JMenu)
{
ProvisionedMessageItemsSwing newMenuItem
= new ProvisionedMessageItemsSwing(text);
newMenuItem.setName(text);
newMenuItem.addActionListener(this);
((JMenu) menu).add(newMenuItem);
return newMenuItem;
}
else
{
ProvisionedMessageItemsAwt newMenuItem
= new ProvisionedMessageItemsAwt(text);
newMenuItem.setName(text);
newMenuItem.addItemListener(this);
((Menu) menu).add(newMenuItem);
return newMenuItem;
}
}
/**
* Creates the appropriate menu item. Depending on the
* menu.
* @param text the menu item text.
* @param index the index to instert the item, -1 to add it at end.
* @param selected whether the new item to be selected
* @return the item.
*/
private Object createsCustomMessageItem(String text,
int index,
boolean selected)
{
if (menu instanceof JMenu)
{
CustomMessageItemsSwing newMenuItem
= new CustomMessageItemsSwing(text);
if(selected)
newMenuItem.setSelected(true);
newMenuItem.setName(text);
newMenuItem.addActionListener(this);
if(index == -1)
{
((JMenu) menu).add(newMenuItem);
}
else
{
// insert the item on the ix position
((JMenu) menu).insert(newMenuItem, index);
}
return newMenuItem;
}
else
{
CustomMessageItemsAwt newMenuItem
= new CustomMessageItemsAwt(text);
if(selected)
newMenuItem.setState(true);
newMenuItem.setName(text);
newMenuItem.addItemListener(this);
if(index == -1)
{
((Menu) menu).add(newMenuItem);
}
else
{
((Menu) menu).insert(newMenuItem, index);
}
return newMenuItem;
}
}
/**
* Selects the item and changes its text to be bold, fires the change of
* status message and return the name of the object which we've changed.
*
* @param item the item to change
* @return the name of the item.
*/
private String selectMenuItem(Object item, String oldMesage)
{
String name = null;
if(item instanceof JCheckBoxMenuItem)
{
JCheckBoxMenuItem checkItem = (JCheckBoxMenuItem)item;
name = checkItem.getName();
checkItem.setSelected(true);
checkItem.setText("<html><b>" + name + "</b></html>");
}
else if(item instanceof CheckboxMenuItem)
{
CheckboxMenuItem checkItem = (CheckboxMenuItem)item;
name = checkItem.getName();
checkItem.setState(true);
}
fireStatusMessageUpdated(oldMesage, name);
return null;
}
/**
* Listens for changes in the custom status messages and update.
* Compares what is saved in the configuration and update according to that.
* @param evt
*/
public void propertyChange(PropertyChangeEvent evt)
{
if(evt.getPropertyName().equals(CUSTOM_STATUS_MESSAGES_UPDATED_PROP))
{
java.util.List<String> customMessagesProps =
DesktopUtilActivator.getConfigurationService()
.getPropertyNamesByPrefix(CUSTOM_MESSAGES_PREFIX, false);
if(customMessagesProps.isEmpty())
{
// someone cleared all messages, let we do the same
removeAllCustomStatusMessages();
return;
}
// if we are here someone has added new custom message, let's find it
// and add it on the appropriate place
java.util.List<String> customMessages = new ArrayList<String>();
for(String p : customMessagesProps)
{
customMessages.add(
DesktopUtilActivator.getConfigurationService().getString(p));
}
for(Object o : getMenuComponents())
{
if(o instanceof CustomMessageItems)
{
customMessages.remove(((CustomMessageItems)o).getName());
}
}
// ok, in the customMessages has left only the new ones, lets add them
for(String message : customMessages)
{
int ix = getLastCustomMessageIndex();
if(ix == -1)
{
this.addSeparator();
createsCustomMessageItem(message, -1, false);
}
else
{
createsCustomMessageItem(message, ix + 1, false);
}
}
}
else if(evt.getPropertyName().equals(STATUS_MESSAGE_UPDATED_PROP))
{
// ignore updates for different providers
if(!evt.getSource().equals(getDescriptor()))
return;
clearSelectedItems();
for(Object o : getMenuComponents())
{
if(o instanceof JCheckBoxMenuItem)
{
JCheckBoxMenuItem item = (JCheckBoxMenuItem)o;
if(!item.isSelected()
&& item.getName().equals(evt.getNewValue()))
{
item.setSelected(true);
}
}
else if(o instanceof CheckboxMenuItem)
{
CheckboxMenuItem item = (CheckboxMenuItem)o;
if(!item.getState()
&& item.getName().equals(evt.getNewValue()))
{
item.setState(true);
}
}
}
}
}
/**
* Clears resources.
*/
public void dispose()
{
removePropertyChangeListener(this);
noMessageItem = null;
newMessageItem = null;
clearCustomMessageItem = null;
busyMessageItem = null;
brbMessageItem = null;
menu = null;
}
/**
* The icon to use for this menu.
* @return
*/
protected Icon getMenuIcon()
{
return null;
}
/**
* The custom message items.
*/
private interface CustomMessageItems
{
public String getName();
}
/**
* The custom message items for swing impl.
*/
private class CustomMessageItemsSwing
extends JCheckBoxMenuItem
implements CustomMessageItems
{
public CustomMessageItemsSwing(String text)
{
super(text);
}
}
/**
* The custom message items for awt impl.
*/
private class CustomMessageItemsAwt
extends CheckboxMenuItem
implements CustomMessageItems
{
public CustomMessageItemsAwt(String text)
{
super(text);
}
}
/**
* The provisioned message items.
*/
private interface ProvisionedMessageItems
{
public String getName();
}
/**
* The provisioned message items for swing impl.
*/
private class ProvisionedMessageItemsSwing
extends JCheckBoxMenuItem
implements ProvisionedMessageItems
{
public ProvisionedMessageItemsSwing(String text)
{
super(text);
}
}
/**
* The provisioned message items for awt impl.
*/
private class ProvisionedMessageItemsAwt
extends CheckboxMenuItem
implements ProvisionedMessageItems
{
public ProvisionedMessageItemsAwt(String text)
{
super(text);
}
}
}