Rework emoticon menu

This commit is contained in:
Philipp Hörist 2017-06-16 19:36:32 +02:00
parent 0d0671374a
commit 79716f421f
12 changed files with 372 additions and 229 deletions

View File

@ -1,7 +1,7 @@
emoticonsdir = $(pkgdatadir)/data/emoticons emoticonsdir = $(pkgdatadir)/data/emoticons
nobase_dist_emoticons_DATA = \ nobase_dist_emoticons_DATA = \
$(srcdir)/*/*.png \ $(srcdir)/*/*.png \
$(srcdir)/*/*.gif \ $(srcdir)/*/LICENSE \
$(srcdir)/*/emoticons.py $(srcdir)/*/emoticons_theme.py
MAINTAINERCLEANFILES = Makefile.in MAINTAINERCLEANFILES = Makefile.in

View File

@ -664,23 +664,17 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkButton" id="emoticons_button"> <object class="GtkMenuButton" id="emoticons_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_markup" translatable="yes">Show a list of emoticons (Alt+M)</property>
<property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property> <property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
<property name="relief">none</property> <property name="relief">none</property>
<child> <child>
<object class="GtkImage" id="emoticons_button_image"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="icon_name">face-smile</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">1</property>
</object> </object>
</child> </child>
</object> </object>

View File

@ -224,20 +224,17 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkButton" id="emoticons_button"> <object class="GtkMenuButton" id="emoticons_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="has_tooltip">True</property>
<property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property> <property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
<property name="relief">none</property> <property name="relief">none</property>
<child> <child>
<object class="GtkImage" id="emoticons_button_image"> <object class="GtkImage">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-missing-image</property> <property name="icon_name">face-smile</property>
<property name="icon_size">1</property>
</object> </object>
</child> </child>
</object> </object>

View File

@ -4,4 +4,11 @@
#ChatControl-AuthenticationButton { padding-top: 0px; padding-bottom: 0px} #ChatControl-AuthenticationButton { padding-top: 0px; padding-bottom: 0px}
/* VCardWindow */ /* VCardWindow */
.VCard-GtkLinkButton { padding-left: 5px; border-left: none; } .VCard-GtkLinkButton { padding-left: 5px; border-left: none; }
popover#EmoticonPopover button { background: none; border: none; box-shadow:none; padding: 0px;}
popover#EmoticonPopover button > label { font-size: 24px; }
popover#EmoticonPopover flowboxchild > label { font-size: 24px; }
popover#EmoticonPopover notebook label { font-size: 24px; }
popover#EmoticonPopover flowbox { padding-left: 5px; padding-right: 6px; }
popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; }

View File

@ -102,6 +102,8 @@ class ChatControl(ChatControlBase):
self.handlers[id_] = self.actions_button self.handlers[id_] = self.actions_button
self._formattings_button = self.xml.get_object('formattings_button') self._formattings_button = self.xml.get_object('formattings_button')
self.emoticons_button = self.xml.get_object('emoticons_button')
self.toggle_emoticons()
self._add_to_roster_button = self.xml.get_object( self._add_to_roster_button = self.xml.get_object(
'add_to_roster_button') 'add_to_roster_button')
@ -325,8 +327,6 @@ class ChatControl(ChatControlBase):
if (gajim.connections[self.account].connected > 1 and not \ if (gajim.connections[self.account].connected > 1 and not \
self.TYPE_ID == 'pm') or (self.contact.show != 'offline' and \ self.TYPE_ID == 'pm') or (self.contact.show != 'offline' and \
self.TYPE_ID == 'pm'): self.TYPE_ID == 'pm'):
emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_sensitive(True)
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(True) send_button.set_sensitive(True)
# Formatting # Formatting
@ -1663,15 +1663,11 @@ class ChatControl(ChatControlBase):
if contact: if contact:
self.contact = contact self.contact = contact
self.draw_banner() self.draw_banner()
emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_sensitive(True)
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(True) send_button.set_sensitive(True)
def got_disconnected(self): def got_disconnected(self):
# Emoticons button # Emoticons button
emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_sensitive(False)
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(False) send_button.set_sensitive(False)
# Add to roster # Add to roster

View File

@ -43,6 +43,7 @@ import history_window
import notify import notify
import re import re
import emoticons
from common import events from common import events
from common import gajim from common import gajim
from common import helpers from common import helpers
@ -263,12 +264,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = widget.connect('clicked', self._on_history_menuitem_activate) id_ = widget.connect('clicked', self._on_history_menuitem_activate)
self.handlers[id_] = widget self.handlers[id_] = widget
# when/if we do XHTML we will put formatting buttons back
widget = self.xml.get_object('emoticons_button')
widget.set_sensitive(False)
id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
self.handlers[id_] = widget
# Create banner and connect signals # Create banner and connect signals
widget = self.xml.get_object('banner_eventbox') widget = self.xml.get_object('banner_eventbox')
id_ = widget.connect('button-press-event', id_ = widget.connect('button-press-event',
@ -364,13 +359,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.received_history_pos = 0 self.received_history_pos = 0
self.orig_msg = None self.orig_msg = None
# Emoticons menu self.set_emoticon_popover()
# set image no matter if user wants at this time emoticons or not
# (so toggle works ok)
img = self.xml.get_object('emoticons_button_image')
img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
'smile.png'))
self.toggle_emoticons()
# Attach speller # Attach speller
if gajim.config.get('use_speller') and HAS_GTK_SPELL: if gajim.config.get('use_speller') and HAS_GTK_SPELL:
@ -568,6 +557,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
When send button is pressed: send the current message When send button is pressed: send the current message
""" """
message_buffer = self.msg_textview.get_buffer() message_buffer = self.msg_textview.get_buffer()
emoticons.replace_with_codepoint(message_buffer)
start_iter = message_buffer.get_start_iter() start_iter = message_buffer.get_start_iter()
end_iter = message_buffer.get_end_iter() end_iter = message_buffer.get_end_iter()
message = message_buffer.get_text(start_iter, end_iter, False) message = message_buffer.get_text(start_iter, end_iter, False)
@ -588,12 +578,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.parent_win.notebook.event(event) self.parent_win.notebook.event(event)
return True return True
def show_emoticons_menu(self):
if not gajim.config.get('emoticons_theme'):
return
gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
def _on_message_textview_key_press_event(self, widget, event): def _on_message_textview_key_press_event(self, widget, event):
if event.keyval == Gdk.KEY_space: if event.keyval == Gdk.KEY_space:
self.space_pressed = True self.space_pressed = True
@ -683,6 +667,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
event.keyval == Gdk.KEY_KP_Enter: # ENTER event.keyval == Gdk.KEY_KP_Enter: # ENTER
message_textview = widget message_textview = widget
message_buffer = message_textview.get_buffer() message_buffer = message_textview.get_buffer()
emoticons.replace_with_codepoint(message_buffer)
start_iter, end_iter = message_buffer.get_bounds() start_iter, end_iter = message_buffer.get_bounds()
message = message_buffer.get_text(start_iter, end_iter, False) message = message_buffer.get_text(start_iter, end_iter, False)
xhtml = self.msg_textview.get_xhtml() xhtml = self.msg_textview.get_xhtml()
@ -1012,31 +997,26 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def toggle_emoticons(self): def toggle_emoticons(self):
""" """
Hide show emoticons_button and make sure emoticons_menu is always there Hide show emoticons_button
when needed
""" """
emoticons_button = self.xml.get_object('emoticons_button')
if gajim.config.get('emoticons_theme'): if gajim.config.get('emoticons_theme'):
emoticons_button.show() self.emoticons_button.set_no_show_all(False)
emoticons_button.set_no_show_all(False) self.emoticons_button.show()
else: else:
emoticons_button.hide() self.emoticons_button.set_no_show_all(True)
emoticons_button.set_no_show_all(True) self.emoticons_button.hide()
def append_emoticon(self, str_): def set_emoticon_popover(self):
buffer_ = self.msg_textview.get_buffer() if not gajim.config.get('emoticons_theme'):
if buffer_.get_char_count(): return
buffer_.insert_at_cursor(' %s ' % str_)
else: # we are the beginning of buffer
buffer_.insert_at_cursor('%s ' % str_)
self.msg_textview.grab_focus()
def on_emoticons_button_clicked(self, widget): if not self.parent_win:
""" return
Popup emoticons menu
""" popover = emoticons.get_popover()
gajim.interface.emoticon_menuitem_clicked = self.append_emoticon popover.set_callbacks(self.msg_textview)
gajim.interface.popup_emoticons_under_button(widget, self.parent_win) emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_popover(popover)
def on_color_menuitem_activate(self, widget): def on_color_menuitem_activate(self, widget):
color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window) color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
@ -1139,6 +1119,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def set_control_active(self, state): def set_control_active(self, state):
if state: if state:
self.set_emoticon_popover()
jid = self.contact.jid jid = self.contact.jid
if self.was_at_the_end: if self.was_at_the_end:
# we are at the end # we are at the end

View File

@ -657,7 +657,7 @@ class PreferencesWindow:
else: else:
gajim.config.set('emoticons_theme', emot_theme) gajim.config.set('emoticons_theme', emot_theme)
gajim.interface.init_emoticons(need_reload = True) gajim.interface.init_emoticons()
gajim.interface.make_regexps() gajim.interface.make_regexps()
self.toggle_emoticons() self.toggle_emoticons()

View File

@ -48,6 +48,7 @@ from common import helpers
from common import i18n from common import i18n
from calendar import timegm from calendar import timegm
from common.fuzzyclock import FuzzyClock from common.fuzzyclock import FuzzyClock
import emoticons
from htmltextview import HtmlTextView from htmltextview import HtmlTextView
from common.exceptions import GajimGeneralException from common.exceptions import GajimGeneralException
@ -952,22 +953,17 @@ class ConversationTextview(GObject.GObject):
end_iter = iter_ end_iter = iter_
else: else:
end_iter = buffer_.get_end_iter() end_iter = buffer_.get_end_iter()
if gajim.config.get('emoticons_theme') and \ if gajim.config.get('emoticons_theme') and graphics:
possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics: pixbuf = emoticons.get_pixbuf(possible_emot_ascii_caps)
# it's an emoticon if pixbuf:
emot_ascii = possible_emot_ascii_caps # it's an emoticon
anchor = buffer_.create_child_anchor(end_iter) anchor = buffer_.create_child_anchor(end_iter)
img = TextViewImage(anchor, img = TextViewImage(anchor,
GLib.markup_escape_text(special_text)) GLib.markup_escape_text(special_text))
animations = gajim.interface.emoticons_animations img.set_from_pixbuf(pixbuf)
if not emot_ascii in animations: img.show()
animations[emot_ascii] = GdkPixbuf.PixbufAnimation.new_from_file( self.images.append(img)
gajim.interface.emoticons[emot_ascii]) self.tv.add_child_at_anchor(img, anchor)
img.set_from_animation(animations[emot_ascii])
img.show()
self.images.append(img)
# add with possible animation
self.tv.add_child_at_anchor(img, anchor)
elif special_text.startswith('www.') or \ elif special_text.startswith('www.') or \
special_text.startswith('ftp.') or \ special_text.startswith('ftp.') or \
text_is_valid_uri and not is_xhtml_link: text_is_valid_uri and not is_xhtml_link:

309
gajim/emoticons.py Normal file
View File

@ -0,0 +1,309 @@
# -*- coding:utf-8 -*-
#
# Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import importlib.util as imp
from collections import OrderedDict
from gi.repository import GdkPixbuf, Gtk, GLib
MODIFIER_MAX_CHILDREN_PER_LINE = 6
MAX_CHILDREN_PER_LINE = 10
MIN_HEIGHT = 200
pixbufs = dict()
codepoints = dict()
popover_instance = None
log = logging.getLogger('gajim.emoticons')
class SubPixbuf:
height = 24
width = 24
columns = 20
def __init__(self, path):
self.cur_column = 0
self.src_x = 0
self.src_y = 0
self.atlas = GdkPixbuf.Pixbuf.new_from_file(path)
def get_pixbuf(self):
self.src_x = self.cur_column * self.width
subpixbuf = self.atlas.new_subpixbuf(self.src_x, self.src_y, self.width, self.height)
if self.cur_column == self.columns - 1:
self.src_y += self.width
self.cur_column = 0
else:
self.cur_column += 1
return subpixbuf
def load(path):
theme_path = os.path.join(path, 'emoticons_theme.py')
spec = imp.spec_from_file_location("emoticons_theme.py", theme_path)
try:
theme = imp.module_from_spec(spec)
spec.loader.exec_module(theme)
except FileNotFoundError:
log.exception('Emoticons theme not found')
return
if not theme.use_image:
# Use Font to display emoticons
set_popover(theme.emoticons, False)
return True
try:
sub = SubPixbuf(os.path.join(path, 'emoticons.png'))
except GLib.GError:
log.exception('Error while creating subpixbuf')
return False
def add_emoticon(codepoint_, sub, mod_list=None):
pix = sub.get_pixbuf()
for alternate in codepoint_:
codepoints[alternate.upper()] = pix
if pix not in pixbufs:
pixbufs[pix] = alternate.upper()
if mod_list is not None:
mod_list.append(pix)
else:
pixbuf_list.append(pix)
popover_dict = OrderedDict()
try:
for category in theme.emoticons:
if not theme.emoticons[category]:
# Empty category
continue
pixbuf_list = []
for filename, codepoint_ in theme.emoticons[category]:
if codepoint_ is None:
# Category image
pixbuf_list.append(sub.get_pixbuf())
continue
if not filename:
# We have an emoticon with a modifier
mod_list = []
for _, mod_codepoint in codepoint_:
add_emoticon(mod_codepoint, sub, mod_list)
pixbuf_list.append(mod_list)
else:
add_emoticon(codepoint_, sub)
popover_dict[category] = pixbuf_list
except Exception:
log.exception('Error while loading emoticon theme')
return
set_popover(popover_dict, True)
return True
def set_popover(popover_dict, use_image):
global popover_instance
popover_instance = EmoticonPopover(popover_dict, use_image)
def get_popover():
return popover_instance
def get_pixbuf(codepoint_):
try:
return codepoints[codepoint_]
except KeyError:
return None
def get_codepoint(pixbuf_):
try:
return pixbufs[pixbuf_]
except KeyError:
return None
def replace_with_codepoint(buffer_):
if not pixbufs:
# We use font emoticons
return
iter_ = buffer_.get_start_iter()
pix = iter_.get_pixbuf()
def replace(pix):
if pix:
emote = get_codepoint(pix)
if not emote:
return
iter_2 = iter_.copy()
iter_2.forward_char()
buffer_.delete(iter_, iter_2)
buffer_.insert(iter_, emote)
replace(pix)
while iter_.forward_char():
pix = iter_.get_pixbuf()
replace(pix)
class EmoticonPopover(Gtk.Popover):
def __init__(self, emoji_dict, use_image):
super().__init__()
self.set_name('EmoticonPopover')
self.text_widget = None
self.use_image = use_image
notebook = Gtk.Notebook()
self.add(notebook)
self.handler_id = self.connect('key_press_event', self.on_key_press)
for category in emoji_dict:
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_min_content_height(MIN_HEIGHT)
flowbox = Gtk.FlowBox()
flowbox.set_max_children_per_line(MAX_CHILDREN_PER_LINE)
flowbox.connect('child_activated', self.on_emoticon_press)
scrolled_window.add(flowbox)
# Use first entry as a label for the notebook page
if self.use_image:
cat_image = Gtk.Image()
cat_image.set_from_pixbuf(emoji_dict[category][0])
notebook.append_page(scrolled_window, cat_image)
else:
notebook.append_page(scrolled_window, Gtk.Label(label=emoji_dict[category][0]))
# Populate the category with emojis
for pix in emoji_dict[category][1:]:
if isinstance(pix, list):
widget = self.add_emoticon_modifier(pix)
else:
if self.use_image:
widget = Gtk.Image()
widget.set_from_pixbuf(pix)
else:
widget = Gtk.Label(pix)
flowbox.add(widget)
notebook.show_all()
def add_emoticon_modifier(self, pixbuf_list):
button = Gtk.MenuButton()
button.set_relief(Gtk.ReliefStyle.NONE)
if self.use_image:
# We use the first item of the list as image for the button
button.get_child().set_from_pixbuf(pixbuf_list[0])
else:
button.remove(button.get_child())
label = Gtk.Label(pixbuf_list[0])
button.add(label)
button.connect('button-press-event', self.on_modifier_press)
popover = Gtk.Popover()
popover.set_name('EmoticonPopover')
popover.connect('key_press_event', self.on_key_press)
flowbox = Gtk.FlowBox()
flowbox.set_size_request(200, -1)
flowbox.set_max_children_per_line(MODIFIER_MAX_CHILDREN_PER_LINE)
flowbox.connect('child_activated', self.on_emoticon_press)
popover.add(flowbox)
for pix in pixbuf_list[1:]:
if self.use_image:
widget = Gtk.Image()
widget.set_from_pixbuf(pix)
else:
widget = Gtk.Label(pix)
flowbox.add(widget)
flowbox.show_all()
button.set_popover(popover)
return button
def set_callbacks(self, widget):
self.text_widget = widget
# Because the handlers getting disconnected when on_destroy() is called
# we connect them again
if self.handler_id:
self.disconnect(self.handler_id)
self.handler_id = self.connect('key_press_event', self.on_key_press)
def on_key_press(self, widget, event):
self.text_widget.grab_focus()
def on_modifier_press(self, button, event):
if event.button == 3:
button.get_popover().show()
button.get_popover().get_child().unselect_all()
if event.button == 1:
button.get_parent().emit('activate')
if self.use_image:
self.append_emoticon(button.get_child().get_pixbuf())
else:
self.append_emoticon(button.get_child().get_text())
return True
def on_emoticon_press(self, flowbox, child):
GLib.timeout_add(100, flowbox.unselect_all)
if isinstance(child.get_child(), Gtk.MenuButton):
return
if self.use_image:
self.append_emoticon(child.get_child().get_pixbuf())
else:
self.append_emoticon(child.get_child().get_text())
def append_emoticon(self, pix):
buffer_ = self.text_widget.get_buffer()
if buffer_.get_char_count():
buffer_.insert_at_cursor(' ')
insert_mark = buffer_.get_insert()
insert_iter = buffer_.get_iter_at_mark(insert_mark)
if self.use_image:
buffer_.insert_pixbuf(insert_iter, pix)
else:
buffer_.insert(insert_iter, pix)
buffer_.insert_at_cursor(' ')
else: # we are the beginning of buffer
insert_mark = buffer_.get_insert()
insert_iter = buffer_.get_iter_at_mark(insert_mark)
if self.use_image:
buffer_.insert_pixbuf(insert_iter, pix)
else:
buffer_.insert(insert_iter, pix)
buffer_.insert_at_cursor(' ')
def do_destroy(self):
# Remove the references we hold to other objects
self.text_widget = None
# Even though we dont destroy the Popover, handlers are getting
# still disconnected, which makes the handler_id invalid
# FIXME: find out how we can prevent handlers getting disconnected
self.handler_id = None
# Never destroy, creating a new EmoticonPopover is expensive
return True

View File

@ -304,6 +304,9 @@ class GroupchatControl(ChatControlBase):
self.on_actions_button_clicked) self.on_actions_button_clicked)
self.handlers[id_] = self.actions_button self.handlers[id_] = self.actions_button
self.emoticons_button = self.xml.get_object('emoticons_button')
self.toggle_emoticons()
widget = self.xml.get_object('change_nick_button') widget = self.xml.get_object('change_nick_button')
widget.set_sensitive(False) widget.set_sensitive(False)
id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate) id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
@ -1429,8 +1432,6 @@ class GroupchatControl(ChatControlBase):
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(True) send_button.set_sensitive(True)
emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_sensitive(True)
formattings_button = self.xml.get_object('formattings_button') formattings_button = self.xml.get_object('formattings_button')
formattings_button.set_sensitive(True) formattings_button.set_sensitive(True)
change_nick_button = self.xml.get_object('change_nick_button') change_nick_button = self.xml.get_object('change_nick_button')
@ -1441,8 +1442,6 @@ class GroupchatControl(ChatControlBase):
def got_disconnected(self): def got_disconnected(self):
send_button = self.xml.get_object('send_button') send_button = self.xml.get_object('send_button')
send_button.set_sensitive(False) send_button.set_sensitive(False)
emoticons_button = self.xml.get_object('emoticons_button')
emoticons_button.set_sensitive(False)
formattings_button = self.xml.get_object('formattings_button') formattings_button = self.xml.get_object('formattings_button')
formattings_button.set_sensitive(False) formattings_button.set_sensitive(False)
change_nick_button = self.xml.get_object('change_nick_button') change_nick_button = self.xml.get_object('change_nick_button')

View File

@ -87,6 +87,7 @@ from common.connection_handlers_events import OurShowEvent, \
from common.connection import Connection from common.connection import Connection
from common.file_props import FilesProp from common.file_props import FilesProp
from common import pep from common import pep
import emoticons
import roster_window import roster_window
import profile_window import profile_window
@ -1839,20 +1840,6 @@ class Interface:
### Methods dealing with emoticons ### Methods dealing with emoticons
################################################################################ ################################################################################
@staticmethod
def image_is_ok(image):
if not os.path.exists(image):
return False
img = Gtk.Image()
try:
img.set_from_file(image)
except Exception:
return False
t = img.get_storage_type()
if t != Gtk.ImageType.PIXBUF and t != Gtk.ImageType.ANIMATION:
return False
return True
@property @property
def basic_pattern_re(self): def basic_pattern_re(self):
if not self._basic_pattern_re: if not self._basic_pattern_re:
@ -1942,7 +1929,7 @@ class Interface:
# NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
# We still allow multiple emoticons side-by-side like :P:P:P # We still allow multiple emoticons side-by-side like :P:P:P
# sort keys by length so :qwe emot is checked before :q # sort keys by length so :qwe emot is checked before :q
keys = sorted(self.emoticons, key=len, reverse=True) keys = sorted(emoticons.codepoints.keys(), key=len, reverse=True)
emoticons_pattern_prematch = '' emoticons_pattern_prematch = ''
emoticons_pattern_postmatch = '' emoticons_pattern_postmatch = ''
emoticon_length = 0 emoticon_length = 0
@ -1981,97 +1968,7 @@ class Interface:
self.invalid_XML_chars = '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\ self.invalid_XML_chars = '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\
'[\ud800-\udfff]|[\ufffe-\uffff]' '[\ud800-\udfff]|[\ufffe-\uffff]'
def popup_emoticons_under_button(self, button, parent_win): def init_emoticons(self):
"""
Popup the emoticons menu under button, located in parent_win
"""
gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
button, parent_win)
def prepare_emoticons_menu(self):
menu = Gtk.Menu()
def emoticon_clicked(w, str_):
if self.emoticon_menuitem_clicked:
self.emoticon_menuitem_clicked(str_)
# don't keep reference to CB of object
# this will prevent making it uncollectable
self.emoticon_menuitem_clicked = None
def selection_done(widget):
# remove reference to CB of object, which will
# make it uncollectable
self.emoticon_menuitem_clicked = None
counter = 0
# Calculate the side lenght of the popup to make it a square
size = int(round(math.sqrt(len(self.emoticons_images))))
for image in self.emoticons_images:
# In Gtk 3.6, Gtk.MenuItem() doesn't contain a label child
item = Gtk.MenuItem.new_with_label('q')
img = Gtk.Image()
if isinstance(image[1], GdkPixbuf.PixbufAnimation):
img.set_from_animation(image[1])
else:
img.set_from_pixbuf(image[1])
c = item.get_child()
item.remove(c)
item.add(img)
item.connect('activate', emoticon_clicked, image[0])
# add tooltip with ascii
item.set_tooltip_text(image[0])
menu.attach(item, counter % size, counter % size + 1,
counter / size, counter / size + 1)
counter += 1
menu.connect('selection-done', selection_done)
menu.show_all()
return menu
def _init_emoticons(self, path, need_reload = False):
#initialize emoticons dictionary and unique images list
self.emoticons_images = list()
self.emoticons = dict()
self.emoticons_animations = dict()
sys.path.insert(0, path)
import emoticons
try:
if need_reload:
# we need to reload else that doesn't work when changing
# emoticons set
import imp
imp.reload(emoticons)
emots = emoticons.emoticons
self.emoticons_sorting = None
try:
self.emoticons_sorting = emoticons.sorting
except:
pass
except Exception as e:
return True
for emot_filename in emots:
emot_file = os.path.join(path, emot_filename)
if not self.image_is_ok(emot_file):
continue
for emot in emots[emot_filename]:
emot = emot
# This avoids duplicated emoticons with the same image eg. :)
# and :-)
if not emot_file in self.emoticons.values():
if emot_file.endswith('.gif'):
pix = GdkPixbuf.PixbufAnimation.new_from_file(emot_file)
else:
pix = GdkPixbuf.Pixbuf.new_from_file_at_size(emot_file,
16, 16)
self.emoticons_images.append((emot, pix))
self.emoticons[emot.upper()] = emot_file
def emoticons_sorter(item):
try:
return self.emoticons_sorting.index(item[0])
except:
return 0
self.emoticons_images = sorted(self.emoticons_images, key=emoticons_sorter)
del emoticons
sys.path.remove(path)
def init_emoticons(self, need_reload = False):
emot_theme = gajim.config.get('emoticons_theme') emot_theme = gajim.config.get('emoticons_theme')
if not emot_theme: if not emot_theme:
return return
@ -2092,40 +1989,14 @@ class Interface:
transient_for=transient_for) transient_for=transient_for)
gajim.config.set('emoticons_theme', '') gajim.config.set('emoticons_theme', '')
return return
if self._init_emoticons(path, need_reload): if not emoticons.load(path):
dialogs.WarningDialog(_('Emoticons disabled'), dialogs.WarningDialog(
_('Your configured emoticons theme cannot been loaded. You ' _('Emoticons disabled'),
'maybe need to update the format of emoticons.py file. See ' _('Your configured emoticons theme could not be loaded.'
'http://trac.gajim.org/wiki/Emoticons for more details.'), ' See the log for more details.'),
transient_for=transient_for) transient_for=transient_for)
gajim.config.set('emoticons_theme', '') gajim.config.set('emoticons_theme', '')
return return
if len(self.emoticons) == 0:
# maybe old format of emoticons file, try to convert it
try:
import pprint
import emoticons
emots = emoticons.emoticons
fd = open(os.path.join(path, 'emoticons.py'), 'w')
fd.write('emoticons = ')
pprint.pprint( dict([
(file_, [i for i in emots.keys() if emots[i] == file_])
for file_ in set(emots.values())]), fd)
fd.close()
del emoticons
self._init_emoticons(path, need_reload=True)
except Exception:
pass
if len(self.emoticons) == 0:
dialogs.WarningDialog(_('Emoticons disabled'),
_('Your configured emoticons theme cannot been loaded. You '
'maybe need to update the format of emoticons.py file. See '
'http://trac.gajim.org/wiki/Emoticons for more details.'),
transient_for=transient_for)
gajim.config.set('emoticons_theme', '')
if self.emoticons_menu:
self.emoticons_menu.destroy()
self.emoticons_menu = self.prepare_emoticons_menu()
################################################################################ ################################################################################
### Methods for opening new messages controls ### Methods for opening new messages controls
@ -2791,9 +2662,6 @@ class Interface:
self.msg_win_mgr = None self.msg_win_mgr = None
self.jabber_state_images = {'16': {}, '24': {}, '32': {}, 'opened': {}, self.jabber_state_images = {'16': {}, '24': {}, '32': {}, 'opened': {},
'closed': {}} 'closed': {}}
self.emoticons_menu = None
# handler when an emoticon is clicked in emoticons_menu
self.emoticon_menuitem_clicked = None
self.minimized_controls = {} self.minimized_controls = {}
self.status_sent_to_users = {} self.status_sent_to_users = {}
self.status_sent_to_groups = {} self.status_sent_to_groups = {}
@ -2822,10 +2690,6 @@ class Interface:
self.emot_and_basic = None self.emot_and_basic = None
self.sth_at_sth_dot_sth = None self.sth_at_sth_dot_sth = None
self.emot_only = None self.emot_only = None
self.emoticons = []
self.emoticons_animations = {}
self.emoticons_images = {}
self.emoticons_sorting = None
cfg_was_read = parser.read() cfg_was_read = parser.read()

View File

@ -438,7 +438,7 @@ class MessageWindow(object):
control.chat_buttons_set_visible(not control.hide_chat_buttons) control.chat_buttons_set_visible(not control.hide_chat_buttons)
return True return True
elif keyval == Gdk.KEY_m: # ALT + M show emoticons menu elif keyval == Gdk.KEY_m: # ALT + M show emoticons menu
control.show_emoticons_menu() control.emoticons_button.get_popover().show()
return True return True
elif keyval == Gdk.KEY_d: # ALT + D show actions menu elif keyval == Gdk.KEY_d: # ALT + D show actions menu
if Gtk.Settings.get_default().get_property( if Gtk.Settings.get_default().get_property(