gajim3/gajim/gtk/status_change.py

568 lines
22 KiB
Python

# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import Gdk
from gi.repository import Gtk
from gajim.common import app
from gajim.common.const import ACTIVITIES
from gajim.common.const import MOODS
from gajim.common.helpers import from_one_line
from gajim.common.helpers import to_one_line
from gajim.common.helpers import remove_invalid_xml_chars
from gajim.common.i18n import _
from .dialogs import TimeoutWindow
from .dialogs import DialogButton
from .dialogs import ConfirmationDialog
from .dialogs import InputDialog
from .util import get_builder
from .util import get_activity_icon_name
if app.is_installed('GSPELL'):
from gi.repository import Gspell # pylint: disable=ungrouped-imports
ACTIVITY_PAGELIST = [
'doing_chores',
'drinking',
'eating',
'exercising',
'grooming',
'having_appointment',
'inactive',
'relaxing',
'talking',
'traveling',
'working',
]
class StatusChange(Gtk.ApplicationWindow, TimeoutWindow):
def __init__(self, callback=None, account=None, status=None, show_pep=True):
Gtk.ApplicationWindow.__init__(self)
countdown_time = app.settings.get('change_status_window_timeout')
TimeoutWindow.__init__(self, countdown_time)
self.set_name('StatusChange')
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(400, 350)
self.set_show_menubar(False)
self.set_transient_for(app.interface.roster.window)
self.title_text = _('Status Message') # TimeoutWindow
self.account = account
self._callback = callback
self._status = status
self._show_pep = show_pep
self._ui = get_builder('status_change_window.ui')
self.add(self._ui.status_stack)
self._status_message = ''
self._pep_dict = {
'activity': '',
'subactivity': '',
'mood': '',
}
self._get_current_status_data()
self._presets = {}
self._get_presets()
if self._status:
self._ui.activity_switch.set_active(self._pep_dict['activity'])
self._ui.activity_page_button.set_sensitive(
self._pep_dict['activity'])
self._ui.mood_switch.set_active(self._pep_dict['mood'])
self._ui.mood_page_button.set_sensitive(self._pep_dict['mood'])
self._message_buffer = self._ui.message_textview.get_buffer()
self._apply_speller()
self._message_buffer.set_text(from_one_line(self._status_message))
self._activity_btns = {}
self._mood_btns = {}
if show_pep:
self._init_activities()
self._draw_activity()
self._init_moods()
self._draw_mood()
else:
self._ui.pep_grid.set_no_show_all(True)
self._ui.pep_grid.hide()
self._message_buffer.connect('changed', self.stop_timeout)
self.connect('key-press-event', self._on_key_press)
self._ui.connect_signals(self)
self.show_all()
self.start_timeout()
def on_timeout(self):
self._change_status()
def _on_key_press(self, _widget, event):
self.stop_timeout()
if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
self._change_status()
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def _apply_speller(self):
if app.settings.get('use_speller') and app.is_installed('GSPELL'):
lang = app.settings.get('speller_language')
gspell_lang = Gspell.language_lookup(lang)
if gspell_lang is None:
gspell_lang = Gspell.language_get_default()
spell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer(
self._message_buffer)
spell_buffer.set_spell_checker(Gspell.Checker.new(gspell_lang))
spell_view = Gspell.TextView.get_from_gtk_text_view(
self._ui.message_textview)
spell_view.set_inline_spell_checking(True)
spell_view.set_enable_language_menu(True)
def _get_current_status_data(self):
'''
Gathers status/pep data for a given account or checks if all accounts
are synchronized. If not, no status message/pep data will be displayed.
'''
if self.account:
client = app.get_client(self.account)
self._status_message = client.status_message
activity_data = client.get_module(
'UserActivity').get_current_activity()
mood_data = client.get_module('UserMood').get_current_mood()
if activity_data:
self._pep_dict['activity'] = activity_data.activity
self._pep_dict['subactivity'] = activity_data.subactivity
if mood_data:
self._pep_dict['mood'] = mood_data.mood
else:
status_messages = []
activities = []
subactivities = []
moods = []
for account in app.connections:
client = app.get_client(account)
if not app.settings.get_account_setting(
client.account, 'sync_with_global_status'):
continue
status_messages.append(client.status_message)
activity_data = client.get_module(
'UserActivity').get_current_activity()
mood_data = client.get_module('UserMood').get_current_mood()
if activity_data:
activities.append(activity_data.activity)
subactivities.append(activity_data.subactivity)
if mood_data:
moods.append(mood_data.mood)
equal_messages = all(x == status_messages[0] for x in
status_messages)
equal_activities = all(x == activities[0] for x in activities)
equal_subactivities = all(x == subactivities[0] for x in
subactivities)
equal_moods = all(x == moods[0] for x in moods)
if status_messages and equal_messages:
self._status_message = status_messages[0]
if activities and equal_activities:
self._pep_dict['activity'] = activities[0]
if subactivities and equal_subactivities:
self._pep_dict['subactivity'] = subactivities[0]
if moods and equal_moods:
self._pep_dict['mood'] = moods[0]
def _get_presets(self):
self._presets = {}
for preset_name in app.settings.get_status_presets():
preset = app.settings.get_status_preset_settings(preset_name)
opts = list(preset.values())
opts[0] = from_one_line(opts[0])
self._presets[preset_name] = opts
self._build_preset_popover()
def _build_preset_popover(self):
child = self._ui.preset_popover.get_children()
if child:
self._ui.preset_popover.remove(child[0])
preset_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
preset_box.get_style_context().add_class('margin-3')
self._ui.preset_popover.add(preset_box)
for preset in self._presets:
button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
preset_button = Gtk.Button()
preset_button.set_name(preset)
preset_button.set_relief(Gtk.ReliefStyle.NONE)
preset_button.set_hexpand(True)
preset_button.add(Gtk.Label(label=preset, halign=Gtk.Align.START))
preset_button.connect('clicked', self._on_preset_select)
button_box.add(preset_button)
remove_button = Gtk.Button()
remove_button.set_name(preset)
remove_button.set_relief(Gtk.ReliefStyle.NONE)
remove_button.set_halign(Gtk.Align.END)
remove_button.add(Gtk.Image.new_from_icon_name(
'edit-delete-symbolic', Gtk.IconSize.MENU))
remove_button.connect('clicked', self._on_preset_remove)
button_box.add(remove_button)
preset_box.add(button_box)
preset_box.show_all()
def _init_activities(self):
group = None
for category in ACTIVITIES:
icon_name = get_activity_icon_name(category)
item = self._ui.get_object(category + '_image')
item.set_from_icon_name(icon_name, Gtk.IconSize.MENU)
item.set_tooltip_text(ACTIVITIES[category]['category'])
category_box = self._ui.get_object(category + '_box')
# Other
act = category + '_other'
if group:
self._activity_btns[act] = Gtk.RadioButton()
self._activity_btns[act].join_group(group)
else:
self._activity_btns[act] = group = Gtk.RadioButton()
icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU)
icon_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
spacing=6)
icon_box.pack_start(icon, False, False, 0)
label = Gtk.Label(
label='<b>%s</b>' % ACTIVITIES[category]['category'])
label.set_use_markup(True)
icon_box.pack_start(label, False, False, 0)
self._activity_btns[act].add(icon_box)
self._activity_btns[act].join_group(self._ui.no_activity_button)
self._activity_btns[act].connect(
'toggled', self._on_activity_toggled, [category, 'other'])
category_box.pack_start(self._activity_btns[act], False, False, 0)
activities = list(ACTIVITIES[category].keys())
activities.sort()
for activity in activities:
if activity == 'category':
continue
act = category + '_' + activity
if group:
self._activity_btns[act] = Gtk.RadioButton()
self._activity_btns[act].join_group(group)
else:
self._activity_btns[act] = group = Gtk.RadioButton()
icon_name = get_activity_icon_name(category, activity)
icon = Gtk.Image.new_from_icon_name(
icon_name, Gtk.IconSize.MENU)
label = Gtk.Label(label=ACTIVITIES[category][activity])
icon_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
spacing=6)
icon_box.pack_start(icon, False, False, 0)
icon_box.pack_start(label, False, False, 0)
self._activity_btns[act].join_group(
self._ui.no_activity_button)
self._activity_btns[act].connect(
'toggled', self._on_activity_toggled, [category, activity])
self._activity_btns[act].add(icon_box)
category_box.pack_start(
self._activity_btns[act], False, False, 0)
if not self._pep_dict['activity']:
self._ui.no_activity_button.set_active(True)
if self._pep_dict['activity'] in ACTIVITIES:
if self._pep_dict['subactivity'] not in ACTIVITIES[
self._pep_dict['activity']]:
self._pep_dict['subactivity'] = 'other'
self._activity_btns[
self._pep_dict['activity'] + '_' + self._pep_dict[
'subactivity']].set_active(True)
self._ui.activity_notebook.set_current_page(
ACTIVITY_PAGELIST.index(self._pep_dict['activity']))
def _draw_activity(self):
if self._pep_dict['activity'] in ACTIVITIES:
if (self._pep_dict['subactivity'] in
ACTIVITIES[self._pep_dict['activity']]):
icon_name = get_activity_icon_name(
self._pep_dict['activity'],
self._pep_dict['subactivity'])
self._ui.activity_image.set_from_icon_name(
icon_name, Gtk.IconSize.MENU)
self._ui.activity_button_label.set_text(
ACTIVITIES[self._pep_dict['activity']][
self._pep_dict['subactivity']])
self._activity_btns[
self._pep_dict['activity'] + '_' + self._pep_dict[
'subactivity']].set_active(True)
self._ui.activity_notebook.set_current_page(
ACTIVITY_PAGELIST.index(self._pep_dict['activity']))
else:
icon_name = get_activity_icon_name(self._pep_dict['activity'])
self._ui.activity_image.set_from_icon_name(
icon_name, Gtk.IconSize.MENU)
self._ui.activity_button_label.set_text(
ACTIVITIES[self._pep_dict['activity']]['category'])
else:
self._ui.activity_image.set_from_pixbuf(None)
self._ui.activity_button_label.set_text(_('No activity'))
def _init_moods(self):
self._ui.no_mood_button.set_mode(False)
self._ui.no_mood_button.connect(
'clicked', self._on_mood_button_clicked, None)
x_position = 1
y_position = 0
# Order them first
moods = []
for mood in MOODS:
moods.append(mood)
moods.sort()
for mood in moods:
image = Gtk.Image.new_from_icon_name(
'mood-%s' % mood, Gtk.IconSize.MENU)
self._mood_btns[mood] = Gtk.RadioButton()
self._mood_btns[mood].join_group(self._ui.no_mood_button)
self._mood_btns[mood].set_mode(False)
self._mood_btns[mood].add(image)
self._mood_btns[mood].set_relief(Gtk.ReliefStyle.NONE)
self._mood_btns[mood].set_tooltip_text(MOODS[mood])
self._mood_btns[mood].connect(
'clicked', self._on_mood_button_clicked, mood)
self._ui.moods_grid.attach(
self._mood_btns[mood], x_position, y_position, 1, 1)
# Calculate the next position
x_position += 1
if x_position >= 11:
x_position = 0
y_position += 1
if self._pep_dict['mood'] in MOODS:
self._mood_btns[self._pep_dict['mood']].set_active(True)
self._ui.mood_label.set_text(MOODS[self._pep_dict['mood']])
else:
self._ui.mood_label.set_text(_('No mood selected'))
def _draw_mood(self):
if self._pep_dict['mood'] in MOODS:
self._ui.mood_image.set_from_icon_name(
'mood-%s' % self._pep_dict['mood'], Gtk.IconSize.MENU)
self._ui.mood_button_label.set_text(
MOODS[self._pep_dict['mood']])
self._mood_btns[self._pep_dict['mood']].set_active(True)
self._ui.mood_label.set_text(MOODS[self._pep_dict['mood']])
else:
self._ui.mood_image.set_from_pixbuf(None)
self._ui.mood_button_label.set_text(_('No mood'))
self._ui.mood_label.set_text(_('No mood selected'))
def _on_preset_select(self, widget):
self.stop_timeout()
self._ui.preset_popover.popdown()
name = widget.get_name()
self._message_buffer.set_text(self._presets[name][0])
self._pep_dict['activity'] = self._presets[name][1]
self._pep_dict['subactivity'] = self._presets[name][2]
self._pep_dict['mood'] = self._presets[name][3]
self._draw_activity()
self._draw_mood()
self._ui.activity_switch.set_active(self._pep_dict['activity'])
self._ui.activity_page_button.set_sensitive(self._pep_dict['activity'])
self._ui.mood_switch.set_active(self._pep_dict['mood'])
self._ui.mood_page_button.set_sensitive(self._pep_dict['mood'])
def _on_preset_remove(self, widget):
self.stop_timeout()
name = widget.get_name()
app.settings.remove_status_preset(name)
self._get_presets()
def _on_save_as_preset_clicked(self, _widget):
self.stop_timeout()
start_iter, finish_iter = self._message_buffer.get_bounds()
message_text = self._message_buffer.get_text(
start_iter, finish_iter, True)
def _on_save_preset(preset_name):
msg_text_one_line = to_one_line(message_text)
if not preset_name:
preset_name = msg_text_one_line
def _on_set_config():
activity = ''
subactivity = ''
mood = ''
if self._ui.activity_switch.get_active():
activity = self._pep_dict['activity']
subactivity = self._pep_dict['subactivity']
if self._ui.mood_switch.get_active():
mood = self._pep_dict['mood']
app.settings.set_status_preset_setting(
preset_name, 'message', msg_text_one_line)
app.settings.set_status_preset_setting(
preset_name, 'activity', activity)
app.settings.set_status_preset_setting(
preset_name, 'subactivity', subactivity)
app.settings.set_status_preset_setting(
preset_name, 'mood', mood)
self._get_presets()
if preset_name in self._presets:
ConfirmationDialog(
_('Overwrite'),
_('Overwrite Status Message?'),
_('This name is already in use. Do you want to '
'overwrite this preset?'),
[DialogButton.make('Cancel'),
DialogButton.make('Remove',
text=_('_Overwrite'),
callback=_on_set_config)],
transient_for=self).show()
return
_on_set_config()
InputDialog(
_('Status Preset'),
_('Save status as preset'),
_('Please assign a name to this status message preset'),
[DialogButton.make('Cancel'),
DialogButton.make('Accept',
text=_('_Save'),
callback=_on_save_preset)],
input_str=_('New Status'),
transient_for=self).show()
def _on_activity_page_clicked(self, _widget):
self.stop_timeout()
self._ui.status_stack.set_visible_child_full(
'activity-page',
Gtk.StackTransitionType.SLIDE_LEFT)
def _on_activity_toggled(self, widget, data):
if widget.get_active():
self._pep_dict['activity'] = data[0]
self._pep_dict['subactivity'] = data[1]
def _on_no_activity_toggled(self, _widget):
self._pep_dict['activity'] = ''
self._pep_dict['subactivity'] = ''
def _on_mood_page_clicked(self, _widget):
self.stop_timeout()
self._ui.status_stack.set_visible_child_full(
'mood-page',
Gtk.StackTransitionType.SLIDE_LEFT)
def _on_mood_button_clicked(self, _widget, data):
if data:
self._ui.mood_label.set_text(MOODS[data])
else:
self._ui.mood_label.set_text(_('No mood selected'))
self._pep_dict['mood'] = data
def _on_back_clicked(self, _widget):
self._ui.status_stack.set_visible_child_full(
'status-page',
Gtk.StackTransitionType.SLIDE_RIGHT)
self._draw_activity()
self._draw_mood()
def _on_activity_switch(self, switch, *args):
self.stop_timeout()
self._ui.activity_page_button.set_sensitive(switch.get_active())
def _on_mood_switch(self, switch, *args):
self.stop_timeout()
self._ui.mood_page_button.set_sensitive(switch.get_active())
def _send_user_mood(self):
mood = None
if self._ui.mood_switch.get_active():
mood = self._pep_dict['mood']
if self.account is None:
for client in app.get_available_clients():
if not app.settings.get_account_setting(
client.account, 'sync_with_global_status'):
continue
client.set_user_mood(mood)
else:
client = app.get_client(self.account)
client.set_user_mood(mood)
def _send_user_activity(self):
activity = None
if self._ui.activity_switch.get_active():
activity = (self._pep_dict['activity'],
self._pep_dict['subactivity'])
if self.account is None:
for client in app.get_available_clients():
if not app.settings.get_account_setting(
client.account, 'sync_with_global_status'):
continue
client.set_user_activity(activity)
else:
client = app.get_client(self.account)
client.set_user_activity(activity)
def _send_status_and_message(self, message):
if self.account is not None:
app.interface.roster.send_status(self.account,
self._status,
message)
return
for account in app.connections:
if not app.settings.get_account_setting(
account, 'sync_with_global_status'):
continue
app.interface.roster.send_status(account, self._status, message)
def _change_status(self, *args):
self.stop_timeout()
beg, end = self._message_buffer.get_bounds()
message = self._message_buffer.get_text(beg, end, True).strip()
message = remove_invalid_xml_chars(message)
if self._show_pep:
self._send_user_activity()
self._send_user_mood()
if self._callback is not None:
self._callback(message)
else:
self._send_status_and_message(message)
self.destroy()