diff --git a/data/glade/gc_control_popup_menu.glade b/data/glade/gc_control_popup_menu.glade index 537c4daad40615567ec0f49a2dde1452aeeb44ca..af8dba9826e097ec793d36aaedcf0a5e0af5ba20 100644 --- a/data/glade/gc_control_popup_menu.glade +++ b/data/glade/gc_control_popup_menu.glade @@ -12,7 +12,7 @@ <property name="use_underline">True</property> <child internal-child="image"> - <widget class="GtkImage" id="image1359"> + <widget class="GtkImage" id="image1378"> <property name="visible">True</property> <property name="stock">gtk-justify-fill</property> <property name="icon_size">1</property> @@ -31,7 +31,7 @@ <property name="use_underline">True</property> <child internal-child="image"> - <widget class="GtkImage" id="image1360"> + <widget class="GtkImage" id="image1379"> <property name="visible">True</property> <property name="stock">gtk-preferences</property> <property name="icon_size">1</property> @@ -44,13 +44,33 @@ </widget> </child> + <child> + <widget class="GtkImageMenuItem" id="destroy_room_menuitem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Destroy room</property> + <property name="use_underline">True</property> + + <child internal-child="image"> + <widget class="GtkImage" id="image1380"> + <property name="visible">True</property> + <property name="stock">gtk-delete</property> + <property name="icon_size">1</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + </child> + </widget> + </child> + <child> <widget class="GtkImageMenuItem" id="change_subject_menuitem"> <property name="label" translatable="yes">Change _Subject</property> <property name="use_underline">True</property> <child internal-child="image"> - <widget class="GtkImage" id="image1361"> + <widget class="GtkImage" id="image1381"> <property name="visible">True</property> <property name="stock">gtk-edit</property> <property name="icon_size">1</property> @@ -69,7 +89,7 @@ <property name="use_underline">True</property> <child internal-child="image"> - <widget class="GtkImage" id="image1362"> + <widget class="GtkImage" id="image1382"> <property name="visible">True</property> <property name="stock">gtk-redo</property> <property name="icon_size">1</property> @@ -88,7 +108,7 @@ <property name="use_underline">True</property> <child internal-child="image"> - <widget class="GtkImage" id="image1363"> + <widget class="GtkImage" id="image1383"> <property name="visible">True</property> <property name="stock">gtk-add</property> <property name="icon_size">1</property> diff --git a/src/common/connection.py b/src/common/connection.py index 45c91ed12539d4ef32aec6e1b84dea82e1585327..6142270775c75653e3b269628f58ad930e1f1f93 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -1160,10 +1160,24 @@ class Connection(ConnectionHandlers): self.connection.send(msg_iq) def request_gc_config(self, room_jid): + if not self.connection: + return iq = common.xmpp.Iq(typ = 'get', queryNS = common.xmpp.NS_MUC_OWNER, to = room_jid) self.connection.send(iq) + def destroy_gc_room(self, room_jid, reason = '', jid = ''): + if not self.connection: + return + iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER, + to = room_jid) + destroy = iq.getTag('query').setTag('destroy') + if reason: + destroy.setTagData('reason', reason) + if jid: + destroy.setAttr('jid', jid) + self.connection.send(iq) + def send_gc_status(self, nick, jid, show, status): if not self.connection: return diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index b21de9ec6157eb59e32f449d8e58ca446eafc8c5..2d853c84e3c8e336b32bb69bc5c4682fd5769fa5 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -1547,6 +1547,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, timestamp = None is_gc = False # is it a GC presence ? sigTag = None + ns_muc_user_x = None avatar_sha = None # JEP-0172 User Nickname user_nick = prs.getTagData('nick') @@ -1558,16 +1559,18 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, namespace = x.getNamespace() if namespace.startswith(common.xmpp.NS_MUC): is_gc = True - if namespace == common.xmpp.NS_SIGNED: + if namespace == common.xmpp.NS_MUC_USER and x.getTag('destroy'): + ns_muc_user_x = x + elif namespace == common.xmpp.NS_SIGNED: sigTag = x - if namespace == common.xmpp.NS_VCARD_UPDATE: + elif namespace == common.xmpp.NS_VCARD_UPDATE: avatar_sha = x.getTagData('photo') - if namespace == common.xmpp.NS_DELAY: + elif namespace == common.xmpp.NS_DELAY: # JEP-0091 tim = prs.getTimestamp() tim = time.strptime(tim, '%Y%m%dT%H:%M:%S') timestamp = time.localtime(timegm(tim)) - if namespace == 'http://delx.cjb.net/protocol/roster-subsync': + elif namespace == 'http://delx.cjb.net/protocol/roster-subsync': # see http://trac.gajim.org/ticket/326 agent = gajim.get_server_from_jid(jid_stripped) if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact @@ -1667,10 +1670,24 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, else: # save sha in mem NOW self.vcard_shas[who] = avatar_sha + if ns_muc_user_x: + # Room has been destroyed. see + # http://www.xmpp.org/extensions/xep-0045.html#destroyroom + reason = _('Room has been destroyed') + destroy = ns_muc_user_x.getTag('destroy') + r = destroy.getTagData('reason') + if r: + reason += ' (%s)' % r + jid = destroy.getAttr('jid') + if jid: + reason += '\n' + _('You can join this room instead: %s') % jid + statusCode = 'destroyed' + else: + reason = prs.getReason() + statusCode = prs.getStatusCode() self.dispatch('GC_NOTIFY', (jid_stripped, show, status, resource, prs.getRole(), prs.getAffiliation(), prs.getJid(), - prs.getReason(), prs.getActor(), prs.getStatusCode(), - prs.getNewNick())) + reason, prs.getActor(), statusCode, prs.getNewNick())) return if ptype == 'subscribe': diff --git a/src/dialogs.py b/src/dialogs.py index 279f67debe4fd940909711524aea34405d1081d2..8155753ea343cf78c351ad77b16fd17b8c4e978b 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -1049,6 +1049,62 @@ class InputDialog: self.dialog.destroy() return response +class DubbleInputDialog: + '''Class for Dubble Input dialog''' + def __init__(self, title, label_str1, label_str2, input_str1 = None, + input_str2 = None, is_modal = True, ok_handler = None, + cancel_handler = None): + # if modal is True you also need to call get_response() + # and ok_handler won't be used + self.xml = gtkgui_helpers.get_glade('dubbleinput_dialog.glade') + self.dialog = self.xml.get_widget('dubbleinput_dialog') + label1 = self.xml.get_widget('label1') + self.input_entry1 = self.xml.get_widget('input_entry1') + label2 = self.xml.get_widget('label2') + self.input_entry2 = self.xml.get_widget('input_entry2') + self.dialog.set_title(title) + label1.set_markup(label_str1) + label2.set_markup(label_str2) + self.cancel_handler = cancel_handler + if input_str1: + self.input_entry1.set_text(input_str1) + self.input_entry1.select_region(0, -1) # select all + if input_str2: + self.input_entry2.set_text(input_str2) + self.input_entry2.select_region(0, -1) # select all + + self.is_modal = is_modal + if not is_modal and ok_handler is not None: + self.ok_handler = ok_handler + okbutton = self.xml.get_widget('okbutton') + okbutton.connect('clicked', self.on_okbutton_clicked) + cancelbutton = self.xml.get_widget('cancelbutton') + cancelbutton.connect('clicked', self.on_cancelbutton_clicked) + self.xml.signal_autoconnect(self) + self.dialog.show_all() + + def on_dubbleinput_dialog_destroy(self, widget): + if self.cancel_handler: + self.cancel_handler() + + def on_okbutton_clicked(self, widget): + user_input1 = self.input_entry1.get_text().decode('utf-8') + user_input2 = self.input_entry2.get_text().decode('utf-8') + self.dialog.destroy() + if isinstance(self.ok_handler, tuple): + self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:]) + else: + self.ok_handler(user_input1, user_input2) + + def on_cancelbutton_clicked(self, widget): + self.dialog.destroy() + + def get_response(self): + if self.is_modal: + response = self.dialog.run() + self.dialog.destroy() + return response + class SubscriptionRequestWindow: def __init__(self, jid, text, account, user_nick = None): xml = gtkgui_helpers.get_glade('subscription_request_window.glade') diff --git a/src/groupchat_control.py b/src/groupchat_control.py index d07fecf42ed122fa527c5afcf09397ce25b0025e..ecb93ba4aefedf355160462a60184dcfab6753b7 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -222,6 +222,11 @@ class GroupchatControl(ChatControlBase): self._on_configure_room_menuitem_activate) self.handlers[id] = widget + widget = xm.get_widget('destroy_room_menuitem') + id = widget.connect('activate', + self._on_destroy_room_menuitem_activate) + self.handlers[id] = widget + widget = xm.get_widget('change_subject_menuitem') id = widget.connect('activate', self._on_change_subject_menuitem_activate) @@ -472,18 +477,21 @@ class GroupchatControl(ChatControlBase): sets sensitivity state for configure_room''' menu = self.gc_popup_menu childs = menu.get_children() - # hide chat buttons - childs[5].set_active(self.hide_chat_buttons_current) + # Check compact view menuitem + childs[6].set_active(self.hide_chat_buttons_current) if gajim.gc_connected[self.account][self.room_jid]: c = gajim.contacts.get_gc_contact(self.account, self.room_jid, self.nick) if c.affiliation not in ('owner', 'admin'): childs[1].set_sensitive(False) + if c.affiliation != 'owner': + childs[2].set_sensitive(False) else: # We are not connected to this groupchat, disable unusable menuitems childs[1].set_sensitive(False) childs[2].set_sensitive(False) childs[3].set_sensitive(False) + childs[4].set_sensitive(False) return menu def on_message(self, nick, msg, tim, has_timestamp = False, xhtml = None): @@ -888,6 +896,8 @@ class GroupchatControl(ChatControlBase): os.remove(files[old_file]) os.rename(old_file, files[old_file]) self.print_conversation(s, 'info') + elif statusCode == 'destroyed': # Room has been destroyed + self.print_conversation(reason, 'info') if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)) == 0: @@ -1442,6 +1452,29 @@ class GroupchatControl(ChatControlBase): gajim.interface.instances[self.account]['gc_config'][self.room_jid]\ = config.GroupchatConfigWindow(self.account, self.room_jid) + def _on_destroy_room_menuitem_activate(self, widget): + # Ask for a reason + instance = dialogs.DubbleInputDialog(_('Destroying %s') % self.room_jid, + _('You are going to definitively destroy this room.\n' + 'You may specify a reason below:'), + _('You may also enter an alternate venue:')) + response = instance.get_response() + if response == gtk.RESPONSE_OK: + reason = instance.input_entry1.get_text().decode('utf-8') + jid = instance.input_entry2.get_text().decode('utf-8') + # Test jid + try: + jid = helpers.parse_jid(jid) + except: + ErrorDialog(_('Invalid group chat Jabber ID'), + _('The group chat Jabber ID has not allowed characters.')) + return + else: + # Abord destroy operation + return + gajim.connections[self.account].destroy_gc_room(self.room_jid, reason, + jid) + def _on_bookmark_room_menuitem_activate(self, widget): bm = { 'name': self.name,