# 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 . 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='%s' % 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()