[sip-comm-dev] [PATCH][sip-comm-dev] Jabber chatrooms, with roles


#1

PATCH:
Provides support for Jabber conferences (chatrooms).
Include room properties (password, subject, ...) and participants roles
(admins, moderators, ...)

Hi all,

You can find some archive about this here:
https://sip-communicator.dev.java.net/servlets/ReadMsg?list=dev&msgNo=6955

I've done this work in being very attentive to the XMPP XEP-0045 specification. I hope
my work matches with, and please tell me if you noticed some mismatches.
http://xmpp.org/extensions/xep-0045.html

I've set icons to represents participants roles, feel free to make better ones (SVGs in .zip),
or even offering a (better/another) way to show this information in the GUI.

I would like to thank all the people on this list who have helped me in this work.
Thanks all!

Best regards,
Valentin

jabber-chatrooms.txt (84.3 KB)

jabber-chatrooms-resources.zip (44.3 KB)


#2

Hi Valentin,

Finally found some time to apply your last patch including Jabber chat rooms support. BTW it looks very good!

I remember that you were working on fixing gmail chat rooms and wanted to know if you succeeded. I've tried creating a room through my gmail account, but it didn't work. It blocked on getCanonicalRoomName() with a feature-not-implemented(501) exception. Have you seen this during your tests?

Cheers,
Yana

···

On Sep 16, 2009, at 5:01 PM, Valentin MARTINET wrote:

PATCH:
Provides support for Jabber conferences (chatrooms).
Include room properties (password, subject, ...) and participants roles
(admins, moderators, ...)

Hi all,

You can find some archive about this here:
https://sip-communicator.dev.java.net/servlets/ReadMsg?list=dev&msgNo=6955

I've done this work in being very attentive to the XMPP XEP-0045 specification. I hope
my work matches with, and please tell me if you noticed some mismatches.
http://xmpp.org/extensions/xep-0045.html

I've set icons to represents participants roles, feel free to make better ones (SVGs in .zip),
or even offering a (better/another) way to show this information in the GUI.

I would like to thank all the people on this list who have helped me in this work.
Thanks all!

Best regards,
Valentin

# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: /Users/Valentin/NetBeansProjects/trunk
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and \n newlines.
# Above lines and this line are ignored by the patching process.
Index: resources/images/images.properties
--- resources/images/images.properties Base (BASE)
+++ resources/images/images.properties Locally Modified (Based On LOCAL)
@@ -77,6 +77,16 @@
service.gui.icons.HOLD_STATUS_ICON=resources/images/impl/gui/common/holdStatusIcon.png
service.gui.icons.MUTE_STATUS_ICON=resources/images/impl/gui/common/muteStatusIcon.png
service.gui.icons.DEFAULT_FILE_ICON=resources/images/impl/gui/common/defaultFileIcon.png
+service.gui.icons.CHATROOM_MEMBER_OWNER=resources/images/impl/gui/common/owner.png
+service.gui.icons.CHATROOM_MEMBER_ADMIN=resources/images/impl/gui/common/admin.png
+service.gui.icons.CHATROOM_MEMBER_MODERATOR=resources/images/impl/gui/common/moderator.png
+service.gui.icons.CHATROOM_MEMBER_STANDARD=resources/images/impl/gui/common/standard.png
+service.gui.icons.CHATROOM_MEMBER_GUEST=resources/images/impl/gui/common/guest.png
+service.gui.icons.CHATROOM_MEMBER_SILENT=resources/images/impl/gui/common/silent.png
+service.gui.icons.CHANGE_ROOM_SUBJECT_16x16=resources/images/impl/gui/common/changeSubject16x16.png
+service.gui.icons.CHANGE_NICKNAME_16x16=resources/images/impl/gui/common/changeNickname16x16.png
+service.gui.icons.BAN_16x16=resources/images/impl/gui/common/ban16x16.png
+service.gui.icons.KICK_16x16=resources/images/impl/gui/common/kick16x16.png
\ No newline at end of file

# Status icons
service.gui.statusicons.USER_ONLINE_ICON=resources/images/impl/gui/common/statusicons/online.png
Index: resources/languages/resources.properties
--- resources/languages/resources.properties Base (BASE)
+++ resources/languages/resources.properties Locally Modified (Based On LOCAL)
@@ -79,6 +79,10 @@
service.gui.CALL=Call
service.gui.CALL_VIA=Call via:
service.gui.CANCEL=&Cancel
+service.gui.CHANGE_ROOM_SUBJECT=Change room's subject...
+service.gui.CHANGE_ROOM_SUBJECT_LABEL=In he field below, you can enter the new subject for this room.
+service.gui.CHANGE_NICKNAME=Change nickname...
+service.gui.CHANGE_NICKNAME_LABEL=In he field below, you can enter your new nickname.
service.gui.CHAT_ROOM_ALREADY_JOINED=The {0} chat room is already joined.
service.gui.CHAT_ROOM_CONFIGURATION={0} chat room configuration
service.gui.CHAT_ROOM_CONFIGURATION_FAILED=Failed to obtain the {0} chat room configuration form.
@@ -179,6 +183,11 @@
service.gui.FONT_SIZE=Size
service.gui.FONT_STYLE=Style
service.gui.FONT_UNDERLINE=Underline
+service.gui.GRANT_OWNERSHIP=Grant ownership...
+service.gui.GRANT_ADMIN=Grant administrator...
+service.gui.GRANT_MODERATOR=Grant moderator
+service.gui.GRANT_MEMBERSHIP=Grant membership
+service.gui.GRANT_VOICE=Grant voice
service.gui.HANG_UP=Hang up
service.gui.HELP=&Help
service.gui.HIDE_OFFLINE_CONTACTS=Hide inactive contacts
@@ -295,6 +304,11 @@
service.gui.REQUEST_AUTHORIZATION=&Request authorization
service.gui.REQUEST_AUTHORIZATION_MSG=Can''t add {0} to your Contact List. {0} must authorize your request to add him/her. Please enter your request below.
service.gui.RETRY=Retry
+service.gui.REVOKE_OWNERSHIP=Revoke ownership
+service.gui.REVOKE_ADMIN=Revoke admin
+service.gui.REVOKE_MODERATOR=Revoke moderator
+service.gui.REVOKE_MEMBERSHIP=Revoke membership
+service.gui.REVOKE_VOICE=Revoke voice
service.gui.ROOT_GROUP=Root group
service.gui.SAVE=&Save
service.gui.SEARCH=&Search
@@ -373,6 +387,14 @@
service.gui.SECURITY_WARNING=Security warning
service.gui.SECURITY_ERROR=Security error
service.gui.SPEED=Speed:
+service.gui.ARE_NOW=You are now {0}
+service.gui.IS_NOW={0} is now {1}
+service.gui.SILENT_MEMBER=silent member
+service.gui.GUEST=visitor
+service.gui.MEMBER=member
+service.gui.MODERATOR=moderator
+service.gui.ADMINISTRATOR=administrator
+service.gui.OWNER=owner of the room

service.gui.JANUARY=Jan
service.gui.FEBRUARY=Feb
Index: src/net/java/sip/communicator/impl/gui/main/chat/ChatContactRightButtonMenu.java
--- src/net/java/sip/communicator/impl/gui/main/chat/ChatContactRightButtonMenu.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/ChatContactRightButtonMenu.java Locally Modified (Based On LOCAL)
@@ -32,21 +32,89 @@
{
    private Logger logger = Logger.getLogger(ChatContactRightButtonMenu.class);

- private JMenuItem kickItem = new JMenuItem(
- GuiActivator.getResources().getI18NString("service.gui.KICK"));
+ private static String KICK =
+ GuiActivator.getResources().getI18NString("service.gui.KICK");

- private JMenuItem banItem = new JMenuItem(
- GuiActivator.getResources().getI18NString("service.gui.BAN"));
+ private static String BAN =
+ GuiActivator.getResources().getI18NString("service.gui.BAN");

- private static String KICK_OPERATION = "Kick";
+ private static String CHANGE_ROOM_SUBJECT =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CHANGE_ROOM_SUBJECT");

- private static String BAN_OPERATION = "Ban";
+ private static String CHANGE_NICKNAME =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.CHANGE_NICKNAME");

+ private static String GRANT_OWNERSHIP =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.GRANT_OWNERSHIP");
+
+ private static String GRANT_ADMIN =
+ GuiActivator.getResources().getI18NString("service.gui.GRANT_ADMIN");
+
+ private static String GRANT_MODERATOR =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.GRANT_MODERATOR");
+
+ private static String GRANT_MEMBERSHIP =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.GRANT_MEMBERSHIP");
+
+ private static String GRANT_VOICE =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.GRANT_VOICE");
+
+ private static String REVOKE_OWNERSHIP =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.REVOKE_OWNERSHIP");
+
+ private static String REVOKE_ADMIN =
+ GuiActivator.getResources().getI18NString("service.gui.REVOKE_ADMIN");
+
+ private static String REVOKE_MODERATOR =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.REVOKE_MODERATOR");
+
+ private static String REVOKE_MEMBERSHIP =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.REVOKE_MEMBERSHIP");
+
+ private static String REVOKE_VOICE =
+ GuiActivator.getResources().getI18NString(
+ "service.gui.REVOKE_VOICE");
+
+ private JMenuItem kickItem = new JMenuItem(KICK);
+ private JMenuItem banItem = new JMenuItem(BAN);
+
+ private JMenuItem changeRoomSubjectItem = new JMenuItem(CHANGE_ROOM_SUBJECT);
+ private JMenuItem changeNicknameItem = new JMenuItem(CHANGE_NICKNAME);
+
+ private JMenuItem grantOwnershipItem = new JMenuItem(GRANT_OWNERSHIP);
+ private JMenuItem grantAdminItem = new JMenuItem(GRANT_ADMIN);
+ private JMenuItem grantMembershipItem = new JMenuItem(GRANT_MEMBERSHIP);
+ private JMenuItem grantModeratorItem = new JMenuItem(GRANT_MODERATOR);
+ private JMenuItem grantVoiceItem = new JMenuItem(GRANT_VOICE);
+
+ private JMenuItem revokeOwnershipItem = new JMenuItem(REVOKE_OWNERSHIP);
+ private JMenuItem revokeAdminItem = new JMenuItem(REVOKE_ADMIN);
+ private JMenuItem revokeMembershipItem = new JMenuItem(REVOKE_MEMBERSHIP);
+ private JMenuItem revokeModeratorItem = new JMenuItem(REVOKE_MODERATOR);
+ private JMenuItem revokeVoiceItem = new JMenuItem(REVOKE_VOICE);
+
    private ChatPanel chatPanel;

+ /**
+ * The contact associated with this menu
+ */
    private ChatContact chatContact;

    /**
+ * The Chatroom in which the chatContact is currently participating.
+ */
+ private ChatRoom room;
+
+ /**
     * Creates an instance of <tt>ChatRoomsListRightButtonMenu</tt>.
     */
    public ChatContactRightButtonMenu(ChatPanel chatPanel,
@@ -56,6 +124,9 @@

        this.chatContact = chatContact;

+ this.room =
+ ((ConferenceChatSession)chatPanel.getChatSession()).getChatRoom();
+
        this.setLocation(getLocation());

        this.init();
@@ -66,22 +137,184 @@
     */
    private void init()
    {
- this.add(kickItem);
- this.add(banItem);
-
- this.kickItem.setName("kick");
- this.banItem.setName("ban");
-
        this.kickItem.setMnemonic(
            GuiActivator.getResources().getI18nMnemonic("service.gui.KICK"));
-
        this.banItem.setMnemonic(
            GuiActivator.getResources().getI18nMnemonic("service.gui.BAN"));
+ this.grantAdminItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.GRANT_ADMIN"));
+ this.grantMembershipItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.GRANT_MEMBERSHIP"));
+ this.grantModeratorItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.GRANT_MODERATOR"));
+ this.grantOwnershipItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.GRANT_OWNERSHIP"));
+ this.grantVoiceItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.GRANT_VOICE"));
+ this.revokeAdminItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.REVOKE_ADMIN"));
+ this.revokeMembershipItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.REVOKE_MEMBERSHIP"));
+ this.revokeModeratorItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.REVOKE_MODERATOR"));
+ this.revokeOwnershipItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.REVOKE_OWNERSHIP"));
+ this.revokeVoiceItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.REVOKE_VOICE"));
+ this.changeNicknameItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.CHANGE_NICKNAME"));
+ this.changeNicknameItem.setMnemonic(
+ GuiActivator.getResources().getI18nMnemonic(
+ "service.gui.CHANGE_ROOM_SUBJECT"));

        this.kickItem.addActionListener(this);
        this.banItem.addActionListener(this);
+ this.changeNicknameItem.addActionListener(this);
+ this.changeRoomSubjectItem.addActionListener(this);
+ this.grantAdminItem.addActionListener(this);
+ this.grantMembershipItem.addActionListener(this);
+ this.grantModeratorItem.addActionListener(this);
+ this.grantOwnershipItem.addActionListener(this);
+ this.grantVoiceItem.addActionListener(this);
+ this.revokeAdminItem.addActionListener(this);
+ this.revokeMembershipItem.addActionListener(this);
+ this.revokeModeratorItem.addActionListener(this);
+ this.revokeOwnershipItem.addActionListener(this);
+ this.revokeVoiceItem.addActionListener(this);
+
+ this.grantOwnershipItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_OWNER), 16, 16));
+ this.grantAdminItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_ADMIN), 16, 16));
+ this.grantMembershipItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_STANDARD), 16, 16));
+ this.grantModeratorItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(
+ ImageLoader.CHATROOM_MEMBER_MODERATOR), 16, 16));
+ this.grantVoiceItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_STANDARD), 16, 16));
+ this.revokeAdminItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_STANDARD), 16, 16));
+ this.revokeMembershipItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_GUEST), 16, 16));
+ this.revokeModeratorItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_STANDARD), 16, 16));
+ this.revokeOwnershipItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_ADMIN), 16, 16));
+ this.revokeVoiceItem.setIcon(ImageUtils.getScaledRoundedIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_SILENT), 16, 16));
+
+ this.kickItem.setIcon(new ImageIcon(ImageLoader.getImage(
+ ImageLoader.KICK_ICON_16x16)));
+ this.banItem.setIcon(new ImageIcon(ImageLoader.getImage(
+ ImageLoader.BAN_ICON_16x16)));
+ this.changeNicknameItem.setIcon(new ImageIcon(ImageLoader.getImage(
+ ImageLoader.CHANGE_NICKNAME_ICON_16x16)));
+ this.changeRoomSubjectItem.setIcon(new ImageIcon(ImageLoader.getImage(
+ ImageLoader.CHANGE_ROOM_SUBJECT_ICON_16x16)));
+
+ int roleIndex = ((ChatRoomMember)
+ chatContact.getDescriptor()).getRole().getRoleIndex();
+ String roleName = ((ChatRoomMember)
+ chatContact.getDescriptor()).getRole().getRoleName();
+
+ if(chatContact.getName().equals(room.getUserNickname()))
+ {
+ roleName = room.getUserRole().getRoleName();
+ roleIndex = room.getUserRole().getRoleIndex();
    }

+ JLabel jl_username =
+ new JLabel(" "+chatContact.getName()+" ("+roleName+") ");
+ jl_username.setFont(jl_username.getFont().deriveFont(Font.BOLD));
+
+ this.add(jl_username);
+ this.addSeparator();
+
+ OperationSetPersistentPresence opSet = (OperationSetPersistentPresence)
+ room.getParentProvider().getOperationSet(
+ OperationSetPersistentPresence.class);
+
+ Contact c = opSet.findContactByID(room.getUserNickname());
+
+ // Here we build the menu when the local user cell renderer is clicked:
+ if(chatContact.getName().equals(room.getUserNickname()))
+ {
+ if(roleIndex >= 50)
+ {
+ // It means we are at least a moderator, so we can change room's
+ // subject:
+ this.add(this.changeRoomSubjectItem);
+ }
+
+ this.add(this.changeNicknameItem);
+ }
+ else
+ {
+ if(room.getUserRole().getRoleIndex() >= 50)
+ {
+ if(roleIndex <= 40)
+ {
+ this.add(this.kickItem);
+
+ // Admins and owners can ban members:
+ if(room.getUserRole().getRoleIndex() >= 60 && roleIndex < 50)
+ {
+ this.add(this.banItem);
+ }
+ this.addSeparator();
+ }
+
+ // we must at least be a moderator for managing voice rights
+ if(roleIndex <= 20)
+ this.add(this.grantVoiceItem);
+ else if(roleIndex == 40 || roleIndex == 30)
+ this.add(this.revokeVoiceItem);
+ }
+
+ if(room.getUserRole().getRoleIndex() >= 60)
+ {
+ // we must at least be an admin to manage membership
+ if(roleIndex < 40)
+ this.add(this.grantMembershipItem);
+ else if(roleIndex == 40)
+ this.add(this.revokeMembershipItem);
+
+ if(roleIndex < 50) // room admins can edit moderators list
+ this.add(this.grantModeratorItem);
+ else if(roleIndex == 50)
+ this.add(this.revokeModeratorItem);
+ }
+
+ // only room owners can edit admins list
+ if(room.getUserRole().getRoleIndex() == 70)
+ {
+ if(roleIndex != 60 && roleIndex >= 30)
+ // room owners can grant members or unaffiliated users as admins
+ this.add(this.grantAdminItem);
+ else if(roleIndex == 60)
+ this.add(this.revokeAdminItem);
+
+ // room owners can edit owners list
+ if(roleIndex != 70 && roleIndex >= 40)
+ this.add(this.grantOwnershipItem);
+ else if(roleIndex == 70)
+ this.add(this.revokeOwnershipItem);
+ }
+ }
+ }
+
    /**
     * Handles the <tt>ActionEvent</tt>. Determines which menu item was
     * selected and makes the appropriate operations.
@@ -89,26 +322,94 @@
    public void actionPerformed(ActionEvent e)
    {
        JMenuItem menuItem = (JMenuItem) e.getSource();
- String itemName = menuItem.getName();
+ String itemId = menuItem.getText();

- if (itemName.equals("kick"))
+ if( itemId.equals(KICK) )
+ new ReasonDialog(KICK).setVisible(true);
+
+ else if( itemId.equals(BAN) )
+ new ReasonDialog(BAN).setVisible(true);
+
+ else if( itemId.equals(CHANGE_ROOM_SUBJECT) )
+ new ReasonDialog(CHANGE_ROOM_SUBJECT).setVisible(true);
+
+ else if( itemId.equals(CHANGE_NICKNAME) )
+ new ReasonDialog(CHANGE_NICKNAME).setVisible(true);
+
+ else if( itemId.equals(GRANT_VOICE) )
        {
- new ReasonDialog(KICK_OPERATION).setVisible(true);
+ room.grantVoice(chatContact.getName());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.GUEST);
        }
- else if (itemName.equals("ban"))
+ else if( itemId.equals(GRANT_MEMBERSHIP) )
        {
- new ReasonDialog(BAN_OPERATION).setVisible(true);
+ room.grantMembership(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.MEMBER);
        }
+ else if( itemId.equals(GRANT_MODERATOR) )
+ {
+ room.grantModerator(chatContact.getName());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.MODERATOR);
    }
+ else if( itemId.equals(GRANT_ADMIN) )
+ {
+ room.grantAdmin(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.ADMINISTRATOR);
+ }
+ else if( itemId.equals(GRANT_OWNERSHIP) )
+ {
+ room.grantOwnership(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.OWNER);
+ }
+ else if( itemId.equals(REVOKE_OWNERSHIP) )
+ {
+ room.revokeOwnership(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.ADMINISTRATOR);
+ }
+ else if( itemId.equals(REVOKE_ADMIN) )
+ {
+ room.revokeAdmin(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.MEMBER);
+ }
+ else if( itemId.equals(REVOKE_MODERATOR) )
+ {
+ room.revokeModerator(chatContact.getName());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.MEMBER);
+ }
+ else if( itemId.equals(REVOKE_MEMBERSHIP) )
+ {
+ room.revokeMembership(((ChatRoomMember)
+ chatContact.getDescriptor()).getContactAddress());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.GUEST);
+ }
+ else if( itemId.equals(REVOKE_VOICE) )
+ {
+ room.revokeVoice(chatContact.getName());
+ ((ChatRoomMember)chatContact.getDescriptor()).setRole(
+ ChatRoomMemberRole.SILENT_MEMBER);
+ }
+ }

    private class ReasonDialog extends SIPCommDialog
    {
        private JLabel iconLabel = new JLabel(new ImageIcon(
            ImageLoader.getImage(ImageLoader.REASON_DIALOG_ICON)));

- private JLabel infoLabel = new JLabel(
- GuiActivator.getResources()
- .getI18NString("service.gui.SPECIFY_REASON"));
+ private JLabel infoLabel;

        private JLabel reasonLabel = new JLabel(
            GuiActivator.getResources()
@@ -123,11 +424,11 @@
            GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

        private JPanel buttonsPanel
- = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

- private JPanel titlePanel = new JPanel(new BorderLayout(10, 10));
+ private JPanel titlePanel = new TransparentPanel(new BorderLayout(10, 10));

- private JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
+ private JPanel mainPanel = new TransparentPanel(new BorderLayout(10, 10));

        private String operationType;

@@ -135,9 +436,18 @@
        {
            super(chatPanel.getChatWindow());

- this.setTitle(
- GuiActivator.getResources().getI18NString("service.gui.REASON"));
+ if(operation.equals(CHANGE_ROOM_SUBJECT))
+ infoLabel = new JLabel(GuiActivator.getResources().getI18NString(
+ "service.gui.CHANGE_ROOM_SUBJECT_LABEL"));
+ else if(operation.equals(CHANGE_NICKNAME))
+ infoLabel = new JLabel( GuiActivator.getResources().getI18NString(
+ "service.gui.CHANGE_NICKNAME_LABEL"));
+ else
+ infoLabel = new JLabel(GuiActivator.getResources().getI18NString(
+ "service.gui.SPECIFY_REASON"));

+ this.setTitle(operation);
+
            this.operationType = operation;

            this.buttonsPanel.add(okButton);
@@ -167,16 +477,38 @@
                        return;
                    }

- if (operationType.equals(KICK_OPERATION))
+ if (operationType.equals(KICK))
                    {
                        new KickParticipantThread(chatRoom,
                            reasonField.getText()).start();
                    }
- else if (operationType.equals(BAN_OPERATION))
+ else if (operationType.equals(BAN))
                    {
                        new BanParticipantThread(chatRoom,
                            reasonField.getText()).start();
                    }
+ else if (operationType.equals(CHANGE_ROOM_SUBJECT))
+ {
+ try
+ {
+ room.setSubject(reasonField.getText().trim());
+ }
+ catch (OperationFailedException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ else if (operationType.equals(CHANGE_NICKNAME))
+ {
+ try
+ {
+ room.setUserNickname(reasonField.getText().trim());
+ }
+ catch (OperationFailedException ex)
+ {
+ ex.printStackTrace();
+ }
+ }

                    ReasonDialog.this.setVisible(false);
                }
@@ -196,8 +528,14 @@
            this.titlePanel.add(infoLabel, BorderLayout.CENTER);

            this.mainPanel.add(titlePanel, BorderLayout.NORTH);
+
+ if(operationType.equals(KICK) || operationType.equals(BAN))
            this.mainPanel.add(reasonLabel, BorderLayout.WEST);
+ else
+ this.mainPanel.add(new JLabel(" "), BorderLayout.WEST);
+
            this.mainPanel.add(reasonField, BorderLayout.CENTER);
+ this.mainPanel.add(new JLabel(" "), BorderLayout.EAST);
            this.mainPanel.add(buttonsPanel, BorderLayout.SOUTH);

            this.mainPanel.setBorder(
Index: src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java
--- src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java Locally Modified (Based On LOCAL)
@@ -14,6 +14,7 @@
import java.io.*;
import java.util.*;

+import java.util.logging.Level;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
@@ -32,6 +33,7 @@
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.swing.*;
import net.java.sip.communicator.util.swing.SwingWorker; // disambiguation
+import org.jivesoftware.smack.util.StringUtils;

/**
* The <tt>ChatPanel</tt> is the panel, where users can write and send messages,
@@ -50,6 +52,8 @@
    implements ChatSessionRenderer,
                Chat,
                ChatConversationContainer,
+ ChatRoomMemberRoleListener,
+ ChatRoomLocalUserRoleListener,
                FileTransferStatusListener
{
    private static final Logger logger = Logger.getLogger(ChatPanel.class);
@@ -124,7 +128,6 @@
        super(new BorderLayout());

        this.chatWindow = chatWindow;
-
        this.conversationPanel = new ChatConversationPanel(this);
        this.conversationPanel.setPreferredSize(new Dimension(400, 200));
        this.conversationPanel.getChatTextPane()
@@ -163,7 +166,6 @@
    public void setChatSession(ChatSession chatSession)
    {
        this.chatSession = chatSession;
-
        if ((this.chatSession != null)
                && this.chatSession.isContactListSupported())
        {
@@ -242,16 +244,19 @@
        else if (chatSession instanceof ConferenceChatSession)
        {
            removeChatTransportSelectorBox();
+ ((ConferenceChatSession)chatSession).getChatRoom().
+ addLocalUserRoleListener(this);
+ ((ConferenceChatSession)chatSession).getChatRoom().
+ addMemberRoleListener(this);

-
// We don't add the subject panel for now. It takes too much space
// and is not used.
-// subjectPanel
-// = new ChatRoomSubjectPanel( chatWindow,
-// (ConferenceChatSession) chatSession);
- // The subject panel is added here, because it's specific for the
- // multi user chat and is not contained in the single chat chat panel.
-// this.add(subjectPanel, BorderLayout.NORTH);
+ subjectPanel
+ = new ChatRoomSubjectPanel( chatWindow,
+ (ConferenceChatSession) chatSession);
+// The subject panel is added here, because it's specific for the
+// multi user chat and is not contained in the single chat chat panel.
+ this.add(subjectPanel, BorderLayout.NORTH);
        }

        if (chatContactListPanel != null)
@@ -363,6 +368,72 @@
    }

    /**
+ * Returns the corresponding role description to the given role index.
+ *
+ * @param roleIndex to role index to analyse
+ * @return String the corresponding role description
+ */
+ public String getRoleDescription(int roleIndex)
+ {
+ String role = "";
+
+ switch(roleIndex)
+ {
+ case 70: role = GuiActivator.getResources().getI18NString(
+ "service.gui.OWNER");
+ break;
+ case 60: role = GuiActivator.getResources().getI18NString(
+ "service.gui.ADMINISTRATOR");
+ break;
+ case 50: role = GuiActivator.getResources().getI18NString(
+ "service.gui.MODERATOR");
+ break;
+ case 40: role = GuiActivator.getResources().getI18NString(
+ "service.gui.MEMBER");
+ break;
+ case 30: role = GuiActivator.getResources().getI18NString(
+ "service.gui.GUEST");
+ break;
+ case 20: role = GuiActivator.getResources().getI18NString(
+ "service.gui.SILENT_MEMBER");
+ break;
+ default:;
+ }
+
+ return role;
+ }
+
+ /**
+ * Implements the <tt>memberRoleChanged()</tt> method.
+ *
+ * @param evt
+ */
+ public void memberRoleChanged(ChatRoomMemberRoleChangeEvent evt)
+ {
+ this.conversationPanel.appendMessageToEnd(
+ "<DIV identifier=\"message\" style=\"color:#707070;\">"
+ + GuiActivator.getResources().getI18NString("service.gui.IS_NOW",
+ new String[]{evt.getSourceMember().getName(),
+ getRoleDescription(evt.getNewRole().getRoleIndex())})
+ +"</DIV>");
+ }
+
+ /**
+ * Implements the <tt>localUserRoleChanged()</tt> method.
+ *
+ * @param evt
+ */
+ public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt)
+ {
+ this.conversationPanel.appendMessageToEnd(
+ "<DIV identifier=\"message\" style=\"color:#707070;\">"
+ +GuiActivator.getResources().getI18NString("service.gui.ARE_NOW",
+ new String[]{
+ getRoleDescription(evt.getNewRole().getRoleIndex())})
+ +"</DIV>");
+ }
+
+ /**
     * Every time the chat panel is shown we set it as a current chat panel.
     * This is done here and not in the Tab selection listener, because the tab
     * change event is not fired when the user clicks on the close tab button
@@ -1850,7 +1921,7 @@

        if (chatSession instanceof MetaContactChatSession)
        {
- String newChatName = inviteChatTransport.getDisplayName();
+ String newChatName = "multichat-"+StringUtils.randomString(4);

            chatContacts.add(inviteChatTransport.getName());

Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactCellRenderer.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactCellRenderer.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactCellRenderer.java Locally Modified (Based On LOCAL)
@@ -9,21 +9,28 @@

import java.awt.*;

+import java.util.Iterator;
import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import net.java.sip.communicator.impl.gui.main.contactlist.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.ServerStoredDetails.BinaryDetail;
+import net.java.sip.communicator.service.protocol.ServerStoredDetails.GenericDetail;
+import net.java.sip.communicator.util.*;

/**
* The <tt>ChatContactCellRenderer</tt> is the renderer for the chat contact
* list.
*
* @author Yana Stamcheva
+ * @author Valentin Martinet
*/
public class ChatContactCellRenderer
    extends ContactListCellRenderer
{
+ @Override
    public Component getListCellRendererComponent( JList list,
                                                    Object value,
                                                    int index,
@@ -34,7 +41,8 @@

        this.photoLabel.setIcon(null);

- ChatContact chatContact = (ChatContact) value;
+ final ChatContact chatContact = (ChatContact) value;
+ final ChatRoomMember member = (ChatRoomMember) chatContact.getDescriptor();

        this.setPreferredSize(new Dimension(20, 30));

@@ -49,8 +57,9 @@
        this.nameLabel.setFont(this.getFont().deriveFont(Font.PLAIN));
        this.nameLabel.setText(displayName);

-// statusIcon.setImage(Constants.getStatusIcon();
-// this.nameLabel.setIcon(statusIcon);
+ if(member.getRole() != null)
+ this.nameLabel.setIcon(
+ ChatContactRoleIcon.getRoleIcon(member.getRole()));

        if (contactForegroundColor != null)
            this.nameLabel.setForeground(contactForegroundColor);
@@ -60,8 +69,62 @@
        ImageIcon avatar = chatContact.getAvatar();

        if (avatar != null)
+ {
            this.photoLabel.setIcon(avatar);
+ }
+ else
+ {
+ if(chatContact.getName().equals(
+ member.getChatRoom().getUserNickname()))
+ {
+ // Try to retrieve local user avatar:
+ OperationSetServerStoredAccountInfo opSet =
+ (OperationSetServerStoredAccountInfo)
+ member.getChatRoom().getParentProvider().getOperationSet(
+ OperationSetServerStoredAccountInfo.class);

+ Iterator<GenericDetail> itr = opSet.getAllAvailableDetails();
+ while(itr.hasNext())
+ {
+ GenericDetail detail = itr.next();
+ if(detail instanceof BinaryDetail)
+ {
+ BinaryDetail bin = (BinaryDetail)detail;
+ if(bin.getBytes() != null)
+ this.photoLabel.setIcon(
+ ImageUtils.getScaledRoundedIcon(bin.getBytes(), 25, 25));
+ break;
+ }
+ }
+
+ this.nameLabel.setIcon(ChatContactRoleIcon.getRoleIcon(
+ member.getChatRoom().getUserRole()));
+
+ }
+ else
+ {
+ // Try to retrieve participant avatar:
+ OperationSetPersistentPresence opSet =
+ (OperationSetPersistentPresence)
+ member.getChatRoom().getParentProvider().getOperationSet(
+ OperationSetPersistentPresence.class);
+
+ Contact c = opSet.findContactByID(member.getContactAddress());
+
+ if(opSet != null)
+ {
+ if(c != null)
+ {
+ if(c.getImage() != null)
+ {
+ this.photoLabel.setIcon(
+ ImageUtils.getScaledRoundedIcon(c.getImage(), 25, 25));
+ }
+ }
+ }
+ }
+ }
+
        // We should set the bounds of the cell explicitly in order to
        // make getComponentAt work properly.
        this.setBounds(0, 0, list.getWidth() - 2, 30);
Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactRoleIcon.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactRoleIcon.java Locally New
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatContactRoleIcon.java Locally New
@@ -0,0 +1,56 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.gui.main.chat.conference;
+
+import javax.swing.*;
+import net.java.sip.communicator.impl.gui.utils.ImageLoader;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.resources.ImageID;
+
+/**
+ * Allows to determine which icon should be chosen for the chat contact
+ * currently participating in a chatroom, regarding to his associated role.
+ *
+ * @author Valentin
+ */
+public class ChatContactRoleIcon
+{
+
+ /**
+ * Returns the role icon which corresponds to the given role.
+ *
+ * @param role the role to analyse
+ * @return Icon the associated Icon with this role
+ */
+ public static Icon getRoleIcon(ChatRoomMemberRole role)
+ {
+ Icon i = null;
+
+ switch(role.getRoleIndex())
+ {
+ case 70: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_OWNER));
+ break;
+ case 60: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_ADMIN));
+ break;
+ case 50: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_MODERATOR));
+ break;
+ case 40: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_STANDARD));
+ break;
+ case 30: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_GUEST));
+ break;
+ default: i = new ImageIcon(
+ ImageLoader.getImage(ImageLoader.CHATROOM_MEMBER_SILENT));
+ }
+
+ return i;
+ }
+}
Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomConfigurationWindow.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomConfigurationWindow.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomConfigurationWindow.java Locally Modified (Based On LOCAL)
@@ -22,6 +22,7 @@
* The configuration window for the chat room.
*
* @author Yana Stamcheva
+ * @author Valentin Martinet
*/
public class ChatRoomConfigurationWindow
    extends SIPCommFrame
@@ -104,6 +105,14 @@
        this.getContentPane().add(tabbedPane, BorderLayout.CENTER);
        this.getContentPane().add(buttonsPanel, BorderLayout.SOUTH);

+ titlePanel.setOpaque(false);
+ tabbedPane.setOpaque(false);
+ buttonsPanel.setOpaque(false);
+ roomOptionsPanel.setOpaque(false);
+ optionsScrollPane.setOpaque(false);
+ mainPanel.setOpaque(false);
+ generalScrollPane.setOpaque(false);
+
        this.roomOptionsPanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createEmptyBorder(15, 15, 15, 15),
            BorderFactory.createTitledBorder(
@@ -275,6 +284,7 @@
            else
            {
                JPanel fieldPanel = new JPanel(new BorderLayout());
+ fieldPanel.setOpaque(false);
                fieldPanel.setBorder(
                    BorderFactory.createEmptyBorder(0, 0, 10, 0));

@@ -404,6 +414,7 @@
        }

        this.dispose();
+
    }

    protected void close(boolean isEscaped)
Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomMemberListPanel.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomMemberListPanel.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomMemberListPanel.java Locally Modified (Based On LOCAL)
@@ -8,6 +8,7 @@

import java.awt.*;

+import java.awt.event.*;
import javax.swing.*;

import net.java.sip.communicator.impl.gui.customcontrols.*;
@@ -38,7 +39,7 @@
     * Creates an instance of <tt>ChatContactListPanel</tt>.
     * @param chat Currently not used
     */
- public ChatRoomMemberListPanel(ChatPanel chat)
+ public ChatRoomMemberListPanel(final ChatPanel chat)
    {
        super(new BorderLayout());

@@ -47,7 +48,24 @@
        this.memberList.setModel(memberListModel);
        this.memberList.addKeyListener(new CListKeySearchListener(memberList));
        this.memberList.setCellRenderer(new ChatContactCellRenderer());
+ this.memberList.addMouseListener(new MouseListener(){

+ public void mouseClicked(MouseEvent e)
+ {
+ if(e.getButton() == MouseEvent.BUTTON3)
+ {
+ new ChatContactRightButtonMenu(
+ chat, (ChatContact)memberList.getSelectedValue()).show(
+ memberList, e.getX(), e.getY());
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {}
+ public void mouseReleased(MouseEvent e) {}
+ public void mouseEntered(MouseEvent e) {}
+ public void mouseExited(MouseEvent e) {}
+ });
+
        JScrollPane contactsScrollPane = new SCScrollPane();
        contactsScrollPane.setHorizontalScrollBarPolicy(
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomSubjectPanel.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomSubjectPanel.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomSubjectPanel.java Locally Modified (Based On LOCAL)
@@ -17,15 +17,17 @@
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.swing.*;

/**
* The panel containing the subject of the chat room and the configuration
* button.
*
* @author Yana Stamcheva
+ * @author Valentin Martinet
*/
public class ChatRoomSubjectPanel
- extends JPanel
+ extends TransparentPanel
{
    private Logger logger = Logger.getLogger(ChatRoomSubjectPanel.class);

Index: src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatSession.java
--- src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatSession.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatSession.java Locally Modified (Based On LOCAL)
@@ -89,6 +89,7 @@
        ChatRoom chatRoom = chatRoomWrapper.getChatRoom();
        chatRoom.removeMemberPresenceListener(this);
        chatRoom.removePropertyChangeListener(this);
+ chatRoom.leave();
    }

    /**
@@ -112,6 +113,16 @@
    }

    /**
+ * Returns the chat room.
+ *
+ * @return the chat room.
+ */
+ public ChatRoom getChatRoom()
+ {
+ return chatRoomWrapper.getChatRoom();
+ }
+
+ /**
     * Returns the configuration form corresponding to the chat room.
     *
     * @return the configuration form corresponding to the chat room.
@@ -396,6 +407,20 @@

        if (eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED))
        {
+ // Check if not ever present in the chat room. In some cases, the
+ // considered chatroom member may appear twice in the chat contact list
+ // panel.
+ for(int i=0; i<chatParticipants.size(); i++)
+ {
+ ChatContact cc = chatParticipants.get(i);
+ if(((ChatRoomMember)cc.getDescriptor()).getContactAddress().equals(
+ evt.getChatRoomMember().getContactAddress()))
+ {
+ chatParticipants.remove(i);
+ sessionRenderer.removeChatContact(cc);
+ }
+ }
+
            ConferenceChatContact chatContact
                = new ConferenceChatContact(chatRoomMember);

Index: src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf
--- src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf Locally Modified (Based On LOCAL)
@@ -35,6 +35,7 @@
net.java.sip.communicator.util.swing.border,
net.java.sip.communicator.util.swing.event,
net.java.sip.communicator.util.swing.plaf,
+ org.jivesoftware.smack.util,
javax.accessibility,
javax.imageio,
javax.swing,
Index: src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java
--- src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java Base (BASE)
+++ src/net/java/sip/communicator/impl/gui/utils/ImageLoader.java Locally Modified (Based On LOCAL)
@@ -1048,6 +1048,67 @@
    public static final ImageID USER_OCCUPIED_ICON
        = new ImageID("service.gui.statusicons.USER_OCCUPIED_ICON");

+ /**
+ * Owner chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_OWNER
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_OWNER");
+
+ /**
+ * Admin chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_ADMIN
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_ADMIN");
+
+ /**
+ * Moderator chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_MODERATOR
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_MODERATOR");
+
+ /**
+ * Standard chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_STANDARD
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_STANDARD");
+
+ /**
+ * Guest chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_GUEST
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_GUEST");
+
+ /**
+ * Silent chatroom member.
+ */
+ public static final ImageID CHATROOM_MEMBER_SILENT
+ = new ImageID("service.gui.icons.CHATROOM_MEMBER_SILENT");
+
+ /**
+ * Change room icon.
+ */
+ public static final ImageID CHANGE_ROOM_SUBJECT_ICON_16x16
+ = new ImageID("service.gui.icons.CHANGE_ROOM_SUBJECT_16x16");
+
+ /**
+ * Change nickname icon
+ */
+ public static final ImageID CHANGE_NICKNAME_ICON_16x16
+ = new ImageID("service.gui.icons.CHANGE_NICKNAME_16x16");
+
+ /**
+ * Ban icon.
+ */
+ public static final ImageID BAN_ICON_16x16
+ = new ImageID("service.gui.icons.BAN_16x16");
+
+ /**
+ * Kick icon.
+ */
+ public static final ImageID KICK_ICON_16x16
+ = new ImageID("service.gui.icons.KICK_16x16");
+
    /*
     * =====================================================================
     * ---------------------------- SMILIES --------------------------------
Index: src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java
--- src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/msghistory/MessageHistoryServiceImpl.java Locally Modified (Based On LOCAL)
@@ -2024,7 +2024,7 @@
    {
        private final ChatRoom chatRoom;
        private final String name;
- private final ChatRoomMemberRole role;
+ private ChatRoomMemberRole role;

        public ChatRoomMemberImpl(String name, ChatRoom chatRoom,
            ChatRoomMemberRole role)
@@ -2068,7 +2068,12 @@
        {
            return null;
        }
+
+ public void setRole(ChatRoomMemberRole newRole)
+ {
+ role = newRole;
    }
+ }

    /**
     * Handles <tt>PropertyChangeEvent</tt> triggered from the modification of
Index: src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java
--- src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java Locally Modified (Based On LOCAL)
@@ -1048,4 +1048,64 @@
         */
        return !isPrivate();
    }
+
+ public ChatRoomMemberRole getUserRole()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
}
+
+ public void setUserRole(ChatRoomMemberRole role) throws OperationFailedException
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantAdmin(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantMembership(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantModerator(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantOwnership(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantVoice(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeAdmin(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeMembership(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeModerator(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeOwnership(String address)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeVoice(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
Index: src/net/java/sip/communicator/impl/protocol/irc/ChatRoomMemberIrcImpl.java
--- src/net/java/sip/communicator/impl/protocol/irc/ChatRoomMemberIrcImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/irc/ChatRoomMemberIrcImpl.java Locally Modified (Based On LOCAL)
@@ -166,4 +166,9 @@
     {
         return null;
     }
+
+ public void setRole(ChatRoomMemberRole role)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
}
+}
\ No newline at end of file
Index: src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java
--- src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java Locally Modified (Based On LOCAL)
@@ -9,7 +9,10 @@
import java.beans.PropertyChangeEvent;
import java.util.*;

+import java.util.logging.Level;
+import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
+import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.Message;
import net.java.sip.communicator.service.protocol.event.*;
@@ -28,6 +31,7 @@
*
* @author Emil Ivov
* @author Yana Stamcheva
+ * @author Valentin Martinet
*/
public class ChatRoomJabberImpl
    implements ChatRoom
@@ -98,12 +102,14 @@
    /**
     * The list of members of this chat room.
     */
- private final Hashtable<String, ChatRoomMember> members = new Hashtable<String, ChatRoomMember>();
+ private final Hashtable<String, ChatRoomMember> members =
+ new Hashtable<String, ChatRoomMember>();

    /**
     * The list of banned members of this chat room.
     */
- private final Hashtable<String, ChatRoomMember> banList = new Hashtable<String, ChatRoomMember>();
+ private final Hashtable<String, ChatRoomMember> banList =
+ new Hashtable<String, ChatRoomMember>();

    /**
     * The nickname of this chat room local user participant.
@@ -111,6 +117,11 @@
    private String nickname;

    /**
+ * The role of this chat room local user participant.
+ */
+ private ChatRoomMemberRole role = null;
+
+ /**
     * The subject of this chat room. Keeps track of the subject changes.
     */
    private String oldSubject;
@@ -142,6 +153,7 @@
            new SmackSubjectUpdatedListener());
        multiUserChat.addMessageListener(new SmackMessageListener());
        multiUserChat.addParticipantStatusListener(new MemberListener());
+ multiUserChat.addUserStatusListener(new UserListener());
    }

    /**
@@ -451,19 +463,29 @@
        throws OperationFailedException
    {
        this.assertConnected();
-
+ nickname = StringUtils.parseName(nickname);
        this.nickname = nickname;

        try
        {
            multiUserChat.join(nickname, new String(password));
+ ChatRoomMemberRole role = null;

+ if(this.getUserRole() == null)
+ {
+ role = ChatRoomMemberRole.GUEST;
+ }
+ else
+ {
+ role = this.getUserRole();
+ }
+
            ChatRoomMemberJabberImpl member
                = new ChatRoomMemberJabberImpl( this,
                                                nickname,
                                                provider.getAccountID()
                                                    .getAccountAddress(),
- ChatRoomMemberRole.GUEST);
+ role);

            members.put(nickname, member);

@@ -537,7 +559,7 @@
        throws OperationFailedException
    {
        this.assertConnected();
-
+ nickname = StringUtils.parseName(nickname);
        this.nickname = nickname;

        try
@@ -550,14 +572,24 @@
            else
                multiUserChat.join(nickname);

+
            if (members.get(nickname) == null)
            {
+ if(this.getUserRole() == null)
+ {
+ role = ChatRoomMemberRole.GUEST;
+ }
+ else
+ {
+ role = this.getUserRole();
+ }
+
                ChatRoomMemberJabberImpl member
                    = new ChatRoomMemberJabberImpl( this,
                                                    nickname,
                                                    provider.getAccountID()
                                                        .getAccountAddress(),
- ChatRoomMemberRole.GUEST);
+ role);

            members.put(nickname, member);

@@ -707,7 +739,6 @@
    public void leave()
    {
        multiUserChat.leave();
-
// FIXME : do we have to do the following when we leave the room ?

        Iterator<Map.Entry<String, ChatRoomMember>> membersSet
@@ -812,6 +843,27 @@
    }

    /**
+ * Returns local user role in the context of this chatroom.
+ *
+ * @return ChatRoomMemberRole
+ */
+ public ChatRoomMemberRole getUserRole()
+ {
+ return this.role;
+ }
+
+ /**
+ * Sets the new rolefor the local user in the context of this chatroom.
+ *
+ * @param role the new role to be set for the local user
+ */
+ public void setUserRole(ChatRoomMemberRole role)
+ {
+ fireLocalUserRoleEvent(getUserRole(), role);
+ this.role = role;
+ }
+
+ /**
     * Instances of this class should be registered as
     * <tt>ParticipantStatusListener</tt> in smack and translates events .
     */
@@ -1170,6 +1222,148 @@
    }

    /**
+ * A listener that is fired anytime your participant's status in a room is
+ * changed, such as the user being kicked, banned, or granted admin
+ * permissions.
+ */
+ private class UserListener implements UserStatusListener
+ {
+
+ /**
+ * Called when a moderator kicked your user from the room. This means that
+ * you are no longer participanting in the room.
+ *
+ * @param actor the moderator that kicked your user from the room
+ * (e.g. user@host.org).
+ * @param reason the reason provided by the actor to kick you from the
+ * room.
+ */
+ public void kicked(String actor, String reason)
+ {
+ opSetMuc.fireLocalUserPresenceEvent(ChatRoomJabberImpl.this,
+ LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED, reason);
+ leave();
+ }
+
+ /**
+ * Called when a moderator grants voice to your user. This means that you
+ * were a visitor in the moderated room before and now you can participate
+ * in the room by sending messages to all occupants.
+ */
+ public void voiceGranted()
+ {
+ setUserRole(ChatRoomMemberRole.MEMBER);
+ }
+
+ /**
+ * Called when a moderator revokes voice from your user. This means that
+ * you were a participant in the room able to speak and now you are a
+ * visitor that can't send messages to the room occupants.
+ */
+ public void voiceRevoked()
+ {
+ setUserRole(ChatRoomMemberRole.SILENT_MEMBER);
+ }
+
+ /**
+ * Called when an administrator or owner banned your user from the room.
+ * This means that you will no longer be able to join the room unless the
+ * ban has been removed.
+ *
+ * @param actor the administrator that banned your user
+ * (e.g. user@host.org).
+ * @param reason the reason provided by the administrator to banned you.
+ */
+ public void banned(String actor, String reason)
+ {
+ opSetMuc.fireLocalUserPresenceEvent(ChatRoomJabberImpl.this,
+ LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_DROPPED, reason);
+ leave();
+ }
+
+ /**
+ * Called when an administrator grants your user membership to the room.
+ * This means that you will be able to join the members-only room.
+ */
+ public void membershipGranted()
+ {
+ setUserRole(ChatRoomMemberRole.MEMBER);
+ }
+
+ /**
+ * Called when an administrator revokes your user membership to the room.
+ * This means that you will not be able to join the members-only room.
+ */
+ public void membershipRevoked()
+ {
+ setUserRole(ChatRoomMemberRole.GUEST);
+ }
+
+ /**
+ * Called when an administrator grants moderator privileges to your user.
+ * This means that you will be able to kick users, grant and revoke voice,
+ * invite other users, modify room's subject plus all the partcipants
+ * privileges.
+ */
+ public void moderatorGranted()
+ {
+ setUserRole(ChatRoomMemberRole.MODERATOR);
+ }
+
+ /**
+ * Called when an administrator revokes moderator privileges from your
+ * user. This means that you will no longer be able to kick users, grant
+ * and revoke voice, invite other users, modify room's subject plus all
+ * the partcipants privileges.
+ */
+ public void moderatorRevoked()
+ {
+ setUserRole(ChatRoomMemberRole.MEMBER);
+ }
+
+ /**
+ * Called when an owner grants to your user ownership on the room. This
+ * means that you will be able to change defining room features as well as
+ * perform all administrative functions.
+ */
+ public void ownershipGranted()
+ {
+ setUserRole(ChatRoomMemberRole.OWNER);
+ }
+
+ /**
+ * Called when an owner revokes from your user ownership on the room. This
+ * means that you will no longer be able to change defining room features
+ * as well as perform all administrative functions.
+ */
+ public void ownershipRevoked()
+ {
+ setUserRole(ChatRoomMemberRole.ADMINISTRATOR);
+ }
+
+ /**
+ * Called when an owner grants administrator privileges to your user. This
+ * means that you will be able to perform administrative functions such as
+ * banning users and edit moderator list.
+ */
+ public void adminGranted()
+ {
+ setUserRole(ChatRoomMemberRole.ADMINISTRATOR);
+ }
+
+ /**
+ * Called when an owner revokes administrator privileges from your user.
+ * This means that you will no longer be able to perform administrative
+ * functions such as banning users and edit moderator list.
+ */
+ public void adminRevoked()
+ {
+ setUserRole(ChatRoomMemberRole.MEMBER);
+ }
+
+ }
+
+ /**
     * Adds a listener that will be notified of changes in our role in the room
     * such as us being granded operator.
     *
@@ -1452,6 +1646,35 @@
    }

    /**
+ * Creates the corresponding ChatRoomLocalUserRoleChangeEvent and notifies
+ * all <tt>ChatRoomLocalUserRoleListener</tt>s that local user's role has
+ * been changed in this <tt>ChatRoom</tt>.
+ *
+ * @param previousRole the previous role that local user had
+ * @param newRole the new role the local user gets
+ */
+ private void fireLocalUserRoleEvent( ChatRoomMemberRole previousRole,
+ ChatRoomMemberRole newRole)
+ {
+ ChatRoomLocalUserRoleChangeEvent evt
+ = new ChatRoomLocalUserRoleChangeEvent(
+ this, previousRole, newRole);
+
+ logger.trace("Will dispatch the following ChatRoom event: " + evt);
+
+ Iterable<ChatRoomLocalUserRoleListener> listeners;
+ synchronized (localUserRoleListeners)
+ {
+ listeners
+ = new ArrayList<ChatRoomLocalUserRoleListener>(
+ localUserRoleListeners);
+ }
+
+ for (ChatRoomLocalUserRoleListener listener : listeners)
+ listener.localUserRoleChanged(evt);
+ }
+
+ /**
     * Delivers the specified event to all registered message listeners.
     * @param evt the <tt>EventObject</tt> that we'd like delivered to all
     * registered message listeners.
@@ -1507,14 +1730,13 @@
                return;

            ChatRoomMember member = smackParticipantToScMember(msg.getFrom());
-
- if(logger.isDebugEnabled())
- {
+ logger.setLevelDebug();
                logger.debug("Received from "
                             + fromUserName
                             + " the message "
                             + msg.toXML());
- }
+ logger.debug(msg.getFrom());
+ logger.debug(member.toString());

            Message newMessage = createMessage(msg.getBody());

@@ -1570,6 +1792,8 @@
    {
        public void subjectUpdated(String subject, String from)
        {
+ logger.setLevelInfo();
+ logger.info("Subject updated to "+subject);
            ChatRoomPropertyChangeEvent evt
                = new ChatRoomPropertyChangeEvent(
                    ChatRoomJabberImpl.this,
@@ -1748,4 +1972,247 @@
    {
        return (ChatRoomMemberJabberImpl) members.get(jabberID);
    }
+
+ /**
+ * Grants administrator privileges to another user. Room owners may grant
+ * administrator privileges to a member or unaffiliated user. An
+ * administrator is allowed to perform administrative functions such as
+ * banning users and edit moderator list.
+ *
+ * @param jid the bare XMPP user ID of the user to grant administrator
+ * privileges (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs granting administrator privileges
+ * to a user.
+ */
+ public void grantAdmin(String jid)
+ {
+ try
+ {
+ multiUserChat.grantAdmin(jid);
}
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Grants membership to a user. Only administrators are able to grant
+ * membership. A user that becomes a room member will be able to enter a room
+ * of type Members-Only (i.e. a room that a user cannot enter without being
+ * on the member list).
+ *
+ * @param jid the bare XMPP user ID of the user to grant membership
+ * privileges (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs granting membership to a user.
+ */
+ public void grantMembership(String jid)
+ {
+ try
+ {
+ multiUserChat.grantMembership(jid);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Grants moderator privileges to a participant or visitor. Room
+ * administrators may grant moderator privileges. A moderator is allowed to
+ * kick users, grant and revoke voice, invite other users, modify room's
+ * subject plus all the partcipants privileges.
+ *
+ * @param nickname the nickname of the occupant to grant moderator
+ * privileges.
+ *
+ * @throws XMPPException if an error occurs granting moderator privileges to
+ * a user.
+ */
+ public void grantModerator(String nickname)
+ {
+ try
+ {
+ multiUserChat.grantModerator(nickname);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Grants ownership privileges to another user. Room owners may grant
+ * ownership privileges. Some room implementations will not allow to grant
+ * ownership privileges to other users. An owner is allowed to change
+ * defining room features as well as perform all administrative functions.
+ *
+ * @param jid the bare XMPP user ID of the user to grant ownership
+ * privileges (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs granting ownership privileges to
+ * a user.
+ */
+ public void grantOwnership(String jid)
+ {
+ try
+ {
+ multiUserChat.grantOwnership(jid);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Grants voice to a visitor in the room. In a moderated room, a moderator
+ * may want to manage who does and does not have "voice" in the room. To have
+ * voice means that a room occupant is able to send messages to the room
+ * occupants.
+ *
+ * @param nickname the nickname of the visitor to grant voice in the room
+ * (e.g. "john").
+ *
+ * @throws XMPPException if an error occurs granting voice to a visitor. In
+ * particular, a 403 error can occur if the occupant that intended to grant
+ * voice is not a moderator in this room (i.e. Forbidden error); or a 400
+ * error can occur if the provided nickname is not present in the room.
+ */
+ public void grantVoice(String nickname)
+ {
+ try
+ {
+ multiUserChat.grantVoice(nickname);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Revokes administrator privileges from a user. The occupant that loses
+ * administrator privileges will become a member. Room owners may revoke
+ * administrator privileges from a member or unaffiliated user.
+ *
+ * @param jid the bare XMPP user ID of the user to grant administrator
+ * privileges (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs revoking administrator privileges
+ * to a user.
+ */
+ public void revokeAdmin(String jid)
+ {
+ try
+ {
+ multiUserChat.revokeAdmin(jid);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Revokes a user's membership. Only administrators are able to revoke
+ * membership. A user that becomes a room member will be able to enter a room
+ * of type Members-Only (i.e. a room that a user cannot enter without being
+ * on the member list). If the user is in the room and the room is of type
+ * members-only then the user will be removed from the room.
+ *
+ * @param jid the bare XMPP user ID of the user to revoke membership
+ * (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs revoking membership to a user.
+ */
+ public void revokeMembership(String jid)
+ {
+ try
+ {
+ multiUserChat.revokeMembership(jid);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Revokes moderator privileges from another user. The occupant that loses
+ * moderator privileges will become a participant. Room administrators may
+ * revoke moderator privileges only to occupants whose affiliation is member
+ * or none. This means that an administrator is not allowed to revoke
+ * moderator privileges from other room administrators or owners.
+ *
+ * @param nickname the nickname of the occupant to revoke moderator
+ * privileges.
+ *
+ * @throws XMPPException if an error occurs revoking moderator privileges
+ * from a user.
+ */
+ public void revokeModerator(String nickname)
+ {
+ try
+ {
+ multiUserChat.revokeModerator(nickname);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Revokes ownership privileges from another user. The occupant that loses
+ * ownership privileges will become an administrator. Room owners may revoke
+ * ownership privileges. Some room implementations will not allow to grant
+ * ownership privileges to other users.
+ *
+ * @param jid the bare XMPP user ID of the user to revoke ownership
+ * (e.g. "user@host.org").
+ *
+ * @throws XMPPException if an error occurs revoking ownership privileges
+ * from a user.
+ */
+ public void revokeOwnership(String jid)
+ {
+ try
+ {
+ multiUserChat.revokeOwnership(jid);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Revokes voice from a participant in the room. In a moderated room, a
+ * moderator may want to revoke an occupant's privileges to speak. To have
+ * voice means that a room occupant is able to send messages to the room
+ * occupants.
+ * @param nickname the nickname of the participant to revoke voice
+ * (e.g. "john").
+ *
+ * @throws XMPPException if an error occurs revoking voice from a
+ * participant. In particular, a 405 error can occur if a moderator or a user
+ * with an affiliation of "owner" or "admin" was tried to revoke his voice
+ * (i.e. Not Allowed error); or a 400 error can occur if the provided
+ * nickname is not present in the room.
+ */
+ public void revokeVoice(String nickname)
+ {
+ try
+ {
+ multiUserChat.revokeVoice(nickname);
+ }
+ catch (XMPPException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+}
Index: src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java
--- src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java Locally Modified (Based On LOCAL)
@@ -25,7 +25,7 @@
    /**
     * The role that this member has in its member room.
     */
- private final ChatRoomMemberRole role;
+ private ChatRoomMemberRole role;

    /**
     * The jabber id of the member (will only be visible to members with
@@ -195,4 +195,9 @@
     {
         this.contact = contact;
     }
+
+ public void setRole(ChatRoomMemberRole role)
+ {
+ this.role = role;
}
+}
Index: src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java
--- src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java Locally Modified (Based On LOCAL)
@@ -13,6 +13,7 @@
import net.java.sip.communicator.util.*;

import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.util.*;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.*;
import org.jivesoftware.smackx.muc.*;
@@ -99,12 +100,14 @@

        if (room == null)
        {
+ logger.setLevelInfo();
+ logger.info("find room returns null");
            MultiUserChat muc = new MultiUserChat(
                getXmppConnection(), getCanonicalRoomName(roomName));

            try
            {
- muc.create(getXmppConnection().getUser());
+ muc.create(StringUtils.parseName(getXmppConnection().getUser()));
                muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
            }
            catch (XMPPException ex)
@@ -115,7 +118,9 @@
                                                   , ex.getCause());
            }
            room = createLocalChatRoomInstance(muc);
+ room.setUserRole(ChatRoomMemberRole.OWNER);
        }
+ room.join();
        return room;
    }

@@ -141,7 +146,6 @@
            // ChatRoomInvitationRejectionListener.
            muc.addInvitationRejectionListener(
                new SmackInvitationRejectionListener(chatRoom));
-
            return chatRoom;
        }
    }
Index: src/net/java/sip/communicator/impl/protocol/mock/MockChatRoom.java
--- src/net/java/sip/communicator/impl/protocol/mock/MockChatRoom.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/mock/MockChatRoom.java Locally Modified (Based On LOCAL)
@@ -592,4 +592,64 @@
    {
        return true;
    }
+
+ public void grantAdmin(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
}
+
+ public void grantMembership(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantModerator(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantOwnership(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void grantVoice(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeAdmin(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeMembership(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeModerator(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeOwnership(String jid)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void revokeVoice(String nickname)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public ChatRoomMemberRole getUserRole()
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void setUserRole(ChatRoomMemberRole role) throws OperationFailedException
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
Index: src/net/java/sip/communicator/impl/protocol/mock/MockChatRoomMember.java
--- src/net/java/sip/communicator/impl/protocol/mock/MockChatRoomMember.java Base (BASE)
+++ src/net/java/sip/communicator/impl/protocol/mock/MockChatRoomMember.java Locally Modified (Based On LOCAL)
@@ -113,4 +113,9 @@
    {
        return contact;
    }
+
+ public void setRole(ChatRoomMemberRole role)
+ {
+ throw new UnsupportedOperationException("Not supported yet.");
}
+}
Index: src/net/java/sip/communicator/service/protocol/ChatRoom.java
--- src/net/java/sip/communicator/service/protocol/ChatRoom.java Base (BASE)
+++ src/net/java/sip/communicator/service/protocol/ChatRoom.java Locally Modified (Based On LOCAL)
@@ -16,6 +16,7 @@
*
* @author Emil Ivov
* @author Yana Stamcheva
+ * @author Valentin Martinet
*/
public interface ChatRoom
{
@@ -142,6 +143,15 @@
    public String getUserNickname();

    /**
+ * Returns the local user's role in the context of this chat room or
+ * <tt>null</tt> if not currently joined.
+ *
+ * @return the role currently being used by the local user in the context of
+ * the chat room.
+ */
+ public ChatRoomMemberRole getUserRole();
+
+ /**
     * Changes the the local user's nickname in the context of this chatroom.
     *
     * @param nickname the new nickname within the room.
@@ -153,6 +163,16 @@
       throws OperationFailedException;

    /**
+ * Changes the the local user's nickname in the context of this chatroom.
+ *
+ * @param role the new role to set for the local user.
+ *
+ * @throws OperationFailedException if an error occurs.
+ */
+ public void setUserRole(ChatRoomMemberRole role)
+ throws OperationFailedException;
+
+ /**
     * Adds a listener that will be notified of changes in our participation in
     * the room such as us being kicked, join, left...
     *
@@ -405,4 +425,115 @@
     * @return true if this chat room is persistent, false otherwise
     */
    public boolean isPersistent();
+
+ /**
+ * Grants administrator privileges to another user. Room owners may grant
+ * administrator privileges to a member or unaffiliated user. An
+ * administrator is allowed to perform administrative functions such as
+ * banning users and edit moderator list.
+ *
+ * @param address the user address of the user to grant administrator
+ * privileges (e.g. "user@host.org").
+ */
+ public void grantAdmin(String address);
+
+ /**
+ * Grants membership to a user. Only administrators are able to grant
+ * membership. A user that becomes a room member will be able to enter a room
+ * of type Members-Only (i.e. a room that a user cannot enter without being
+ * on the member list).
+ *
+ * @param address the user address of the user to grant membership
+ * privileges (e.g. "user@host.org").
+ */
+ public void grantMembership(String address);
+
+ /**
+ * Grants moderator privileges to a participant or visitor. Room
+ * administrators may grant moderator privileges. A moderator is allowed to
+ * kick users, grant and revoke voice, invite other users, modify room's
+ * subject plus all the partcipants privileges.
+ *
+ * @param nickname the nickname of the occupant to grant moderator
+ * privileges.
+ */
+ public void grantModerator(String nickname);
+
+ /**
+ * Grants ownership privileges to another user. Room owners may grant
+ * ownership privileges. Some room implementations will not allow to grant
+ * ownership privileges to other users. An owner is allowed to change
+ * defining room features as well as perform all administrative functions.
+ *
+ * @param address the user address of the user to grant ownership
+ * privileges (e.g. "user@host.org").
+ */
+ public void grantOwnership(String address);
+
+ /**
+ * Grants voice to a visitor in the room. In a moderated room, a moderator
+ * may want to manage who does and does not have "voice" in the room. To have
+ * voice means that a room occupant is able to send messages to the room
+ * occupants.
+ *
+ * @param nickname the nickname of the visitor to grant voice in the room
+ * (e.g. "john").
+ */
+ public void grantVoice(String nickname);
+
+ /**
+ * Revokes administrator privileges from a user. The occupant that loses
+ * administrator privileges will become a member. Room owners may revoke
+ * administrator privileges from a member or unaffiliated user.
+ *
+ * @param address the user address of the user to grant administrator
+ * privileges (e.g. "user@host.org").
+ */
+ public void revokeAdmin(String address);
+
+ /**
+ * Revokes a user's membership. Only administrators are able to revoke
+ * membership. A user that becomes a room member will be able to enter a room
+ * of type Members-Only (i.e. a room that a user cannot enter without being
+ * on the member list). If the user is in the room and the room is of type
+ * members-only then the user will be removed from the room.
+ *
+ * @param address the user address of the user to revoke membership
+ * (e.g. "user@host.org").
+ */
+ public void revokeMembership(String address);
+
+ /**
+ * Revokes moderator privileges from another user. The occupant that loses
+ * moderator privileges will become a participant. Room administrators may
+ * revoke moderator privileges only to occupants whose affiliation is member
+ * or none. This means that an administrator is not allowed to revoke
+ * moderator privileges from other room administrators or owners.
+ *
+ * @param nickname the nickname of the occupant to revoke moderator
+ * privileges.
+ */
+ public void revokeModerator(String nickname);
+
+ /**
+ * Revokes ownership privileges from another user. The occupant that loses
+ * ownership privileges will become an administrator. Room owners may revoke
+ * ownership privileges. Some room implementations will not allow to grant
+ * ownership privileges to other users.
+ *
+ * @param address the user address of the user to revoke ownership
+ * (e.g. "user@host.org").
+ */
+ public void revokeOwnership(String address);
+
+ /**
+ * Revokes voice from a participant in the room. In a moderated room, a
+ * moderator may want to revoke an occupant's privileges to speak. To have
+ * voice means that a room occupant is able to send messages to the room
+ * occupants.
+ * @param nickname the nickname of the participant to revoke voice
+ * (e.g. "john").
+ */
+ public void revokeVoice(String nickname);
+
}
Index: src/net/java/sip/communicator/service/protocol/ChatRoomMember.java
--- src/net/java/sip/communicator/service/protocol/ChatRoomMember.java Base (BASE)
+++ src/net/java/sip/communicator/service/protocol/ChatRoomMember.java Locally Modified (Based On LOCAL)
@@ -80,4 +80,12 @@
     * the this member in its containing chat room.
     */
    public ChatRoomMemberRole getRole();
+
+ /**
+ * Sets the role of this chat room member in its containing room.
+ *
+ * @param a <tt>ChatRoomMemberRole</tt> instance indicating the role
+ * to set for this member in its containing chat room.
+ */
+ public void setRole(ChatRoomMemberRole role);
}
Index: src/net/java/sip/communicator/service/protocol/ChatRoomMemberRole.java
--- src/net/java/sip/communicator/service/protocol/ChatRoomMemberRole.java Base (BASE)
+++ src/net/java/sip/communicator/service/protocol/ChatRoomMemberRole.java Locally Modified (Based On LOCAL)
@@ -50,13 +50,13 @@
     * send messages/speak.
     */
    public static final ChatRoomMemberRole SILENT_MEMBER
- = new ChatRoomMemberRole("SilentMember", 30);
+ = new ChatRoomMemberRole("SilentMember", 20);

    /**
     * A role implying an explicit ban for the user to join the room.
     */
    public static final ChatRoomMemberRole OUTCAST
- = new ChatRoomMemberRole("Outcast", 20);
+ = new ChatRoomMemberRole("Outcast", 10);

    /**
     * the name of this role.
<jabber-chatrooms-resources.zip>---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net


#3

Hi Yana!

Yana Stamcheva a �crit :

I remember that you were working on fixing gmail chat rooms and wanted to know if you succeeded. I've tried creating a room through my gmail account, but it didn't work. It blocked on getCanonicalRoomName() with a feature-not-implemented(501) exception. Have you seen this during your tests?

This remember me nothing. The fact is that you can't create GMail based conference from SIP Communicator.
I tested with Spark, Gajim, and others, and with each of them it was unpossible.

Therefore, you can join GMail conferences when you receive an invite from someone using the official Google Talk gadget.
This the only case where I've succeed to make a GMail conference, whether I was using Spark, Gajim or SC.

Regards,
Valentin

···

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net


#4

IIRC, Google uses a different protocol (not XEP-0045) for its chatrooms.

Peter

···

On 11/27/09 5:38 AM, Valentin MARTINET wrote:

Hi Yana!

Yana Stamcheva a écrit :

I remember that you were working on fixing gmail chat rooms and wanted
to know if you succeeded. I've tried creating a room through my gmail
account, but it didn't work. It blocked on getCanonicalRoomName() with
a feature-not-implemented(501) exception. Have you seen this during
your tests?

This remember me nothing. The fact is that you can't create GMail based
conference from SIP Communicator.
I tested with Spark, Gajim, and others, and with each of them it was
unpossible.

Therefore, you can join GMail conferences when you receive an invite
from someone using the official Google Talk gadget.
This the only case where I've succeed to make a GMail conference,
whether I was using Spark, Gajim or SC.

--
Peter Saint-Andre
https://stpeter.im/


#5

Hi Valentin, Peter,

Thank you both for the explanation!

Valentin, your last patch is now applied, committed and ack-ed. It's cool to have role support in the group chat:) Thank you for the good work and your endless patience:)

Before committing your code I've done the following minor modifications:

- added some optimizations in the ui components code to avoid duplicatation
- made the ChatRoomMemberRole an enum
- resized role icons to fit 16x16
- added missing comments

For more information you could have a look at my commit (revision 6424).

I've noticed that all grantXXX methods in ChatRoomJabberImpl claim to throw an XMPPException, which is not (and should NOT be) thrown from their parent interface methods and results in warnings. Something that you could do is to declare all interface methods to throw an OperationFailedException and just throw this one when catching the XMPPException in Jabber. Also you could replace all printStackTrace() with logger.debug prints.

While testing the Jabber group chat I thought it would be also comfortable if we add the possibility to leave the chat when closing the window (or tab). I know that we've done that only for ad-hoc rooms (Msn, Icq, Yahoo), but now I think that this behavior fits also very well persistent rooms like in Jabber or Irc.

Cheers,
Yana

···

On Nov 30, 2009, at 5:35 PM, Peter Saint-Andre wrote:

On 11/27/09 5:38 AM, Valentin MARTINET wrote:

Hi Yana!

Yana Stamcheva a écrit :

I remember that you were working on fixing gmail chat rooms and wanted
to know if you succeeded. I've tried creating a room through my gmail
account, but it didn't work. It blocked on getCanonicalRoomName() with
a feature-not-implemented(501) exception. Have you seen this during
your tests?

This remember me nothing. The fact is that you can't create GMail based
conference from SIP Communicator.
I tested with Spark, Gajim, and others, and with each of them it was
unpossible.

Therefore, you can join GMail conferences when you receive an invite
from someone using the official Google Talk gadget.
This the only case where I've succeed to make a GMail conference,
whether I was using Spark, Gajim or SC.

IIRC, Google uses a different protocol (not XEP-0045) for its chatrooms.

Peter

--
Peter Saint-Andre
https://stpeter.im/

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@sip-communicator.dev.java.net
For additional commands, e-mail: dev-help@sip-communicator.dev.java.net


#6

Hi Yana,

I've noticed that all grantXXX methods in ChatRoomJabberImpl claim to throw an XMPPException, which is not (and should NOT be) thrown from their parent interface methods and results in warnings. Something that you could do is to declare all interface methods to throw an OperationFailedException and just throw this one when catching the XMPPException in Jabber. Also you could replace all printStackTrace() with logger.debug prints.

Added to my SC's TODO :wink:

While testing the Jabber group chat I thought it would be also comfortable if we add the possibility to leave the chat when closing the window (or tab). I know that we've done that only for ad-hoc rooms (Msn, Icq, Yahoo), but now I think that this behavior fits also very well persistent rooms like in Jabber or Irc.

Yes, me too. Actually, I thought it was done because I remember we talked about it (offlist) some time ago.
Anyway, one more item for my TODOs :slight_smile:

As this patch finalize my work for this year summer (I don't forget unit tests), I just wanted to sincerely
thanks you Yana, Emil, and all the people on this list who helped me!
Working on this OSS was just cool, great, and learnt me so much!
I just want this to be continued!

Kind regards =)

Valentin

···

Le 2 déc. 2009 à 17:56, Yana Stamcheva a écrit :