diff --git a/gajim/application.py b/gajim/application.py index e076abbc5..48789b0fc 100644 --- a/gajim/application.py +++ b/gajim/application.py @@ -160,9 +160,7 @@ class GajimApplication(Gtk.Application): self.interface.roster.window.present() return from gajim.gui_interface import Interface - from gajim import gtkgui_helpers self.interface = Interface() - gtkgui_helpers.load_css() self.interface.run(self) self.add_actions() from gajim import gui_menu_builder diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 82dda0baa..4c87ee452 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -674,7 +674,6 @@ class ChatControl(ChatControlBase): status_reduced = '' status_escaped = GLib.markup_escape_text(status_reduced) - font_attrs, font_attrs_small = self.get_font_attrs() st = app.config.get('displayed_chat_state_notifications') cs = contact.chatstate if cs and st in ('composing_only', 'all'): @@ -685,22 +684,21 @@ class ChatControl(ChatControlBase): else: chatstate = '' - label_text = '%s%s %s' \ - % (font_attrs, name, font_attrs_small, acct_info, chatstate) + label_text = '%s%s %s' \ + % (name, acct_info, chatstate) if acct_info: acct_info = i18n.direction_mark + ' ' + acct_info label_tooltip = '%s%s %s' % (name, acct_info, chatstate) else: - # weight="heavy" size="x-large" - label_text = '%s%s' % \ - (font_attrs, name, font_attrs_small, acct_info) + label_text = '%s%s' % \ + (name, acct_info) if acct_info: acct_info = i18n.direction_mark + ' ' + acct_info label_tooltip = '%s%s' % (name, acct_info) if status_escaped: status_text = self.urlfinder.sub(self.make_href, status_escaped) - status_text = '%s' % (font_attrs_small, status_text) + status_text = '%s' % status_text self.banner_status_label.set_tooltip_text(status) self.banner_status_label.set_no_show_all(False) self.banner_status_label.show() @@ -881,7 +879,7 @@ class ChatControl(ChatControlBase): if self.correcting: self.correcting = False gtkgui_helpers.remove_css_class( - self.msg_textview, 'msgcorrectingcolor') + self.msg_textview, 'gajim-msg-correcting') self.print_conversation(obj.message, self.contact.jid, tim=obj.timestamp, encrypted=obj.encrypted, xep0184_id=xep0184_id, xhtml=obj.xhtml, diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index 8e77b0dd4..7c8f9c347 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -38,6 +38,7 @@ from gi.repository import Gio from gajim import gtkgui_helpers from gajim import message_control from gajim.gtk import NonModalConfirmationDialog +from gajim.gtk.util import convert_rgb_to_hex from gajim import notify import re @@ -52,6 +53,7 @@ from gajim.conversation_textview import ConversationTextview from gajim.message_textview import MessageTextView from gajim.common.contacts import GC_Contact from gajim.common.connection_handlers_events import MessageOutgoingEvent +from gajim.common.const import StyleAttr from gajim.command_system.implementation.middleware import ChatCommandProcessor from gajim.command_system.implementation.middleware import CommandTools @@ -87,40 +89,13 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): keycode_ins = None def make_href(self, match): - url_color = app.config.get('urlmsgcolor') + url_color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) + color = convert_rgb_to_hex(url_color) url = match.group() - if not '://' in url: + if '://' not in url: url = 'http://' + url - return '%s' % (url, - url_color, match.group()) - - def get_font_attrs(self): - """ - Get pango font attributes for banner from theme settings - """ - theme = app.config.get('roster_theme') - bannerfont = app.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = app.config.get_per('themes', theme, 'bannerfontattrs') - - if bannerfont: - font = Pango.FontDescription(bannerfont) - else: - font = Pango.FontDescription('Normal') - if bannerfontattrs: - # B attribute is set by default - if 'B' in bannerfontattrs: - font.set_weight(Pango.Weight.HEAVY) - if 'I' in bannerfontattrs: - font.set_style(Pango.Style.ITALIC) - - font_attrs = 'font_desc="%s"' % font.to_string() - - # in case there is no font specified we use x-large font size - if font.get_size() == 0: - font_attrs = '%s size="x-large"' % font_attrs - font.set_weight(Pango.Weight.NORMAL) - font_attrs_small = 'font_desc="%s" size="small"' % font.to_string() - return (font_attrs, font_attrs_small) + return '%s' % ( + url, color, match.group()) def get_nb_unread(self): jid = self.contact.jid @@ -381,6 +356,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self._nec_ping) app.ged.register_event_handler('sec-label-received', ged.GUI1, self._sec_labels_received) + app.ged.register_event_handler('style-changed', ged.GUI1, + self._style_changed) # This is basically a very nasty hack to surpass the inability # to properly use the super, because of the old code. @@ -557,6 +534,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): self._nec_our_status) app.ged.remove_event_handler('sec-label-received', ged.GUI1, self._sec_labels_received) + app.ged.remove_event_handler('style-changed', ged.GUI1, + self._style_changed) + def on_msg_textview_populate_popup(self, textview, menu): """ @@ -1099,6 +1079,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): def on_clear_formatting_menuitem_activate(self, widget): self.msg_textview.clear_tags() + def _style_changed(self, *args): + self.update_tags() + def update_tags(self): self.conv_textview.update_tags() @@ -1365,14 +1348,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')): self.correcting = True gtkgui_helpers.add_css_class( - self.msg_textview, 'msgcorrectingcolor') + self.msg_textview, 'gajim-msg-correcting') message = history[pos - 1] msg_buf.set_text(message) return if self.correcting: # We were previously correcting gtkgui_helpers.remove_css_class( - self.msg_textview, 'msgcorrectingcolor') + self.msg_textview, 'gajim-msg-correcting') self.correcting = False pos += -1 if direction == 'up' else +1 if pos == -1: diff --git a/gajim/common/app.py b/gajim/common/app.py index 3e2a73697..0df58aa70 100644 --- a/gajim/common/app.py +++ b/gajim/common/app.py @@ -43,6 +43,7 @@ from gajim.common import configpaths from gajim.common import ged as ged_module from gajim.common.contacts import LegacyContactsAPI from gajim.common.events import Events +from gajim.common.css_config import CSSConfig interface = None # The actual interface (the gtk one for the moment) thread_interface = lambda *args: None # Interface to run a thread and then a callback @@ -66,6 +67,8 @@ gajimpaths = configpaths.gajimpaths RecentGroupchat = namedtuple('RecentGroupchat', ['room', 'server', 'nickname']) +css_config = None + os_info = None # used to cache os information transport_type = {} # list the type of transport @@ -625,3 +628,7 @@ def get_app_window(cls, account=None): continue return win return None + +def load_css_config(): + global css_config + css_config = CSSConfig() diff --git a/gajim/common/config.py b/gajim/common/config.py index 8c9be2920..3fb649e1d 100644 --- a/gajim/common/config.py +++ b/gajim/common/config.py @@ -92,13 +92,6 @@ class Config: 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ], 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ], 'use_transports_iconsets': [ opt_bool, True, '', True ], - 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ], - 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ], - 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ], - 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ], - 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ], - 'markedmsgcolor': [ opt_color, '#ff8080', '', True ], - 'urlmsgcolor': [ opt_color, '#204a87', '', True ], 'notif_signin_color': [ opt_color, '#32CD32', _('Contact signed in notification color.') ], # limegreen 'notif_signout_color': [ opt_color, '#FF0000', _('Contact signout notification color') ], # red 'notif_message_color': [ opt_color, '#1E90FF', _('New message notification color.') ], # dodgerblue @@ -108,11 +101,6 @@ class Config: 'notif_invite_color': [ opt_color, '#D2B48C', _('Groupchat invitation notification color') ], # tan1 'notif_status_color': [ opt_color, '#D8BFD8', _('Background color of status changed notification') ], # thistle2 'notif_other_color': [ opt_color, '#FFFFFF', _('Other dialogs color.') ], # white - 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ], - 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ], - 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ], - 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ], - 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ], 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], 'roster_theme': [ opt_str, _('default'), '', True ], 'mergeaccounts': [ opt_bool, False, '', True ], @@ -205,7 +193,6 @@ class Config: 'notify_on_file_complete': [opt_bool, True], 'file_transfers_port': [opt_int, 28011], 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of sent hosts, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')], - 'conversation_font': [opt_str, ''], 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')], 'notify_on_all_muc_messages': [opt_bool, False], 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the notification area.')], @@ -249,9 +236,6 @@ class Config: 'print_status_in_muc': [opt_str, 'none', _('Can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes their status and/or their status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], 'log_contact_status_changes': [opt_bool, False], 'log_xhtml_messages': [opt_bool, False, _('Log XHTML messages instead of plain text messages.')], - 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')], - 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')], - 'restored_messages_color': [opt_color, '#555753'], 'restored_messages_small': [opt_bool, True, _('If true, restored messages will use a smaller font than the default one.')], 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], @@ -438,35 +422,7 @@ class Config: 'bosh_http_pipelining': [ opt_bool, False ], 'bosh_wait_for_restart_response': [ opt_bool, False ], }, {}), - 'themes': ({ - 'accounttextcolor': [ opt_color, 'black', '', True ], - 'accountbgcolor': [ opt_color, 'white', '', True ], - 'accountfont': [ opt_str, '', '', True ], - 'accountfontattrs': [ opt_str, 'B', '', True ], - 'grouptextcolor': [ opt_color, 'black', '', True ], - 'groupbgcolor': [ opt_color, 'white', '', True ], - 'groupfont': [ opt_str, '', '', True ], - 'groupfontattrs': [ opt_str, 'I', '', True ], - 'contacttextcolor': [ opt_color, 'black', '', True ], - 'contactbgcolor': [ opt_color, 'white', '', True ], - 'contactfont': [ opt_str, '', '', True ], - 'contactfontattrs': [ opt_str, '', '', True ], - 'bannertextcolor': [ opt_color, 'black', '', True ], - 'bannerbgcolor': [ opt_color, '', '', True ], - 'bannerfont': [ opt_str, '', '', True ], - 'bannerfontattrs': [ opt_str, 'B', '', True ], - 'msgcorrectingcolor': [opt_color, '#eee8aa'], - # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html - 'state_inactive_color': [ opt_color, 'grey62' ], - 'state_composing_color': [ opt_color, 'green4' ], - 'state_paused_color': [ opt_color, 'mediumblue' ], - 'state_gone_color': [ opt_color, 'grey' ], - - # MUC chat states - 'state_muc_msg_color': [ opt_color, 'mediumblue' ], - 'state_muc_directed_msg_color': [ opt_color, 'red2' ], - }, {}), 'contacts': ({ 'speller_language': [ opt_str, '', _('Language for which misspelled words will be checked')], }, {}), @@ -522,29 +478,6 @@ class Config: 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], } - themes_default = { - # sorted alphanum - _('default'): [ '', '', '', 'B', '', '', '', 'I', '', '', '', '', '', '', - '', 'B' ], - - _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7', - '', 'I', '#000000', '', '', '', '', - '#94aa8c', '', 'B' ], - - _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad', - '', 'I', '#000000', '#efb26b', '', '', '', - '#108abd', '', 'B' ], - - _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94', - '', 'I', '#000000', '', '', '', '', - '#996442', '', 'B' ], - - _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3', - '', 'I', '#000000', '', '', '', '', - '#918caa', '', 'B' ], - - } - proxies_default = { _('Tor'): ['socks5', 'localhost', 9050], } diff --git a/gajim/common/configpaths.py b/gajim/common/configpaths.py index bde9f63d5..cb2873943 100644 --- a/gajim/common/configpaths.py +++ b/gajim/common/configpaths.py @@ -128,6 +128,7 @@ class ConfigPaths: source_paths = [ ('DATA', os.path.join(basedir, 'data')), + ('STYLE', os.path.join(basedir, 'data', 'style')), ('GUI', os.path.join(basedir, 'data', 'gui')), ('ICONS', os.path.join(basedir, 'data', 'icons')), ('HOME', os.path.expanduser('~')), @@ -214,6 +215,10 @@ class ConfigPaths: # Cache paths ('CACHE_DB', 'cache.db', PathLocation.CACHE, PathType.FILE), ('AVATAR', 'avatars', PathLocation.CACHE, PathType.FOLDER), + + # Config paths + ('MY_THEME', 'theme', PathLocation.CONFIG, PathType.FOLDER), + ] for path in paths: diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index 5b30a6156..038540e1c 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -1378,3 +1378,11 @@ class InformationEvent(nec.NetworkIncomingEvent): else: self.args = (self.args,) return True + + +class StyleChanged(nec.NetworkIncomingEvent): + name = 'style-changed' + base_network_events = [] + + def generate(self): + return True diff --git a/gajim/common/const.py b/gajim/common/const.py index ddca6865b..905743bec 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -1,9 +1,12 @@ -from enum import IntEnum, unique +from enum import IntEnum, Enum, unique from collections import namedtuple Option = namedtuple('Option', 'kind label type value name callback data desc enabledif props') Option.__new__.__defaults__ = (None,) * len(Option._fields) +DialogButton = namedtuple('DialogButton', 'text callback action') +DialogButton.__new__.__defaults__ = (None, None) + @unique class OptionKind(IntEnum): @@ -117,6 +120,24 @@ class JIDConstant(IntEnum): NORMAL_TYPE = 0 ROOM_TYPE = 1 +@unique +class StyleAttr(Enum): + COLOR = 'color' + BACKGROUND = 'background' + FONT = 'font' + +@unique +class CSSPriority(IntEnum): + APPLICATION = 600 + APPLICATION_DARK = 601 + DEFAULT_THEME = 610 + DEFAULT_THEME_DARK = 611 + USER_THEME = 650 + +@unique +class ButtonAction(Enum): + DESTRUCTIVE = 'destructive-action' + SUGGESTED = 'suggested-action' @unique class IdleState(IntEnum): diff --git a/gajim/common/css_config.py b/gajim/common/css_config.py new file mode 100644 index 000000000..940ccc17e --- /dev/null +++ b/gajim/common/css_config.py @@ -0,0 +1,492 @@ +# Copyright (C) 2018 Philipp Hörist +# +# 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, either version 3 of the License, or +# (at your option) any later version. +# +# 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 . + +import os +import math +import logging + +import cssutils +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Pango + +from gajim.common import app +from gajim.common import configpaths +from gajim.common.const import StyleAttr, CSSPriority + +log = logging.getLogger('gajim.c.css') + +_settings = Gtk.Settings.get_default() +PREFER_DARK = False +if _settings is not None: + PREFER_DARK = _settings.get_property('gtk-application-prefer-dark-theme') + + +class CSSConfig(): + def __init__(self): + """CSSConfig handles loading and storing of all relevant Gajim style files + + The order in which CSSConfig loads the styles + + 1. gajim.css + 2. gajim-dark.css (Only if gtk-application-prefer-dark-theme = True) + 3. default.css or default-dark.css (from gajim/data/style) + 4. user-theme.css (from ~/.config/Gajim/theme) + + # gajim.css: + + This is the main style and the application default + + # gajim-dark.css + + Has only entrys which we want to override in gajim.css + + # default.css or default-dark.css + + Has all the values that are changeable via UI (see themes.py). + Depending on `gtk-application-prefer-dark-theme` either default.css or + default-dark.css gets loaded + + # user-theme.css + + These are the themes the Themes Dialog stores. Because they are + loaded at last they overwrite everything else. Users should add custom + css here.""" + + # Delete empty rules + cssutils.ser.prefs.keepEmptyRules = False + + # Holds the currently selected theme in the Theme Editor + self._pre_css = None + self._pre_css_path = None + + # Holds the default theme, its used if values are not found + # in the selected theme + self._default_css = None + self._default_css_path = None + + # Holds the currently selected theme + self._css = None + self._css_path = None + + # User Theme CSS Provider + self._provider = Gtk.CssProvider() + + # Cache of recently requested values + self._cache = {} + + # Holds all currently available themes + self.themes = [] + + self._load_css() + self._gather_available_themes() + self._load_default() + self._load_selected() + self._activate_theme() + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + self._provider, + CSSPriority.USER_THEME) + + def _load_css(self): + self._load_css_from_file('gajim.css', CSSPriority.APPLICATION) + if PREFER_DARK: + self._load_css_from_file('gajim-dark.css', + CSSPriority.APPLICATION_DARK) + + self._load_css_from_file('default.css', CSSPriority.DEFAULT_THEME) + if PREFER_DARK: + self._load_css_from_file('default-dark.css', + CSSPriority.DEFAULT_THEME_DARK) + + def _load_css_from_file(self, filename, priority): + path = os.path.join(configpaths.get('STYLE'), filename) + try: + with open(path, "r") as f: + css = f.read() + except Exception as exc: + log.error('Error loading css: %s', exc) + return + self._activate_css(css, priority) + + def _activate_css(self, css, priority): + try: + provider = Gtk.CssProvider() + provider.load_from_data(bytes(css.encode('utf-8'))) + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), + provider, + priority) + except Exception: + log.exception('Error loading application css') + + @staticmethod + def _pango_to_css_weight(number): + # Pango allows for weight values between 100 and 1000 + # CSS allows only full hundred numbers like 100, 200 .. + number = int(number) + if number < 100: + return 100 + if number > 900: + return 900 + return int(math.ceil(number / 100.0)) * 100 + + def _gather_available_themes(self): + files = os.listdir(configpaths.get('MY_THEME')) + self.themes = [file[:-4] for file in files if file.endswith('.css')] + if 'default' in self.themes: + # Ignore user created themes that are named 'default' + self.themes.remove('default') + + @classmethod + def get_theme_path(cls, theme, user=True): + if theme == 'default' and PREFER_DARK: + theme = 'default-dark' + + if user: + return os.path.join(configpaths.get('MY_THEME'), '%s.css' % theme) + return os.path.join(configpaths.get('STYLE'), '%s.css' % theme) + + def _determine_theme_path(self): + # Gets the path of the currently active theme. + # If it does not exist, it falls back to the default theme + theme = app.config.get('roster_theme') + if theme == 'default': + return self.get_theme_path(theme, user=False) + + theme_path = self.get_theme_path(theme) + if not theme or not os.path.exists(theme_path): + log.warning('Theme %s not found, fallback to default', theme) + app.config.set('roster_theme', 'default') + log.info('Use Theme: default') + return self.get_theme_path('default', user=False) + log.info('Use Theme: %s', theme) + return theme_path + + def _load_selected(self, new_path=None): + if new_path is None: + self._css_path = self._determine_theme_path() + else: + self._css_path = new_path + self._css = cssutils.parseFile(self._css_path) + + def _load_default(self): + self._default_css_path = self.get_theme_path('default', user=False) + self._default_css = cssutils.parseFile(self._default_css_path) + + def _load_pre(self, theme): + log.info('Preload theme %s', theme) + self._pre_css_path = self.get_theme_path(theme) + self._pre_css = cssutils.parseFile(self._pre_css_path) + + def _write(self, pre): + path = self._css_path + css = self._css + if pre: + path = self._pre_css_path + css = self._pre_css + with open(path, 'w', encoding='utf-8') as file: + file.write(css.cssText.decode('utf-8')) + + active = self._pre_css_path == self._css_path + if not pre or active: + self._load_selected() + self._activate_theme() + + def set_value(self, selector, attr, value, pre=False): + if attr == StyleAttr.FONT: + # forward to set_font() for convenience + return self.set_font(selector, value, pre) + + if isinstance(attr, StyleAttr): + attr = attr.value + + css = self._css + if pre: + css = self._pre_css + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Set %s %s %s', selector, attr, value) + rule.style[attr] = value + if not pre: + self._add_to_cache(selector, attr, value) + self._write(pre) + return + + # The rule was not found, so we add it to this theme + log.info('Set %s %s %s', selector, attr, value) + rule = cssutils.css.CSSStyleRule(selectorText=selector) + rule.style[attr] = value + css.add(rule) + self._write(pre) + + def set_font(self, selector, description, pre=False): + css = self._css + if pre: + css = self._pre_css + family, size, style, weight = self._get_attr_from_description( + description) + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Set Font for: %s %s %s %s %s', + selector, family, size, style, weight) + rule.style['font-family'] = family + rule.style['font-style'] = style + rule.style['font-size'] = '%spt' % size + rule.style['font-weight'] = weight + + if not pre: + self._add_to_cache( + selector, 'fontdescription', description) + self._write(pre) + return + + # The rule was not found, so we add it to this theme + log.info('Set Font for: %s %s %s %s %s', + selector, family, size, style, weight) + rule = cssutils.css.CSSStyleRule(selectorText=selector) + rule.style['font-family'] = family + rule.style['font-style'] = style + rule.style['font-size'] = '%spt' % size + rule.style['font-weight'] = weight + css.add(rule) + self._write(pre) + + def _get_attr_from_description(self, description): + size = description.get_size() / Pango.SCALE + style = self._get_string_from_pango_style(description.get_style()) + weight = self._pango_to_css_weight(int(description.get_weight())) + family = description.get_family() + return family, size, style, weight + + def _get_default_rule(self, selector, attr): + for rule in self._default_css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Get Default Rule %s', selector) + return rule + + def get_font(self, selector, pre=False): + if pre: + css = self._pre_css + else: + css = self._css + try: + return self._get_from_cache(selector, 'fontdescription') + except KeyError: + pass + + if css is None: + return + + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Get Font for: %s', selector) + style = rule.style.getPropertyValue('font-style') or None + size = rule.style.getPropertyValue('font-size') or None + weight = rule.style.getPropertyValue('font-weight') or None + family = rule.style.getPropertyValue('font-family') or None + + desc = self._get_description_from_css( + family, size, style, weight) + if not pre: + self._add_to_cache(selector, 'fontdescription', desc) + return desc + + def _get_description_from_css(self, family, size, style, weight): + if family is None: + return + desc = Pango.FontDescription() + desc.set_family(family) + if weight is not None: + desc.set_weight(Pango.Weight(int(weight))) + if style is not None: + desc.set_style(self._get_pango_style_from_string(style)) + if size is not None: + desc.set_size(int(size[:-2]) * Pango.SCALE) + return desc + + @staticmethod + def _get_pango_style_from_string(style: str) -> int: + if style == 'normal': + return Pango.Style(0) + if style == 'oblique': + return Pango.Style(1) + # Pango.Style.ITALIC: + return Pango.Style(2) + + @staticmethod + def _get_string_from_pango_style(style: Pango.Style) -> str: + if style == Pango.Style.NORMAL: + return 'normal' + if style == Pango.Style.ITALIC: + return 'italic' + # Pango.Style.OBLIQUE: + return 'oblique' + + def get_value(self, selector, attr, pre=False): + if attr == StyleAttr.FONT: + # forward to get_font() for convenience + return self.get_font(selector, pre) + + if isinstance(attr, StyleAttr): + attr = attr.value + + if pre: + css = self._pre_css + else: + css = self._css + try: + return self._get_from_cache(selector, attr) + except KeyError: + pass + + if css is not None: + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Get %s %s: %s', + selector, attr, rule.style[attr] or None) + value = rule.style.getPropertyValue(attr) or None + if not pre: + self._add_to_cache(selector, attr, value) + return value + + # We didnt find the selector in the selected theme + # search in default theme + if not pre: + rule = self._get_default_rule(selector, attr) + if rule is not None: + self._add_to_cache(selector, attr, rule.style[attr]) + return rule.style[attr] + + def remove_value(self, selector, attr, pre=False): + if attr == StyleAttr.FONT: + # forward to remove_font() for convenience + return self.remove_font(selector, pre) + + if isinstance(attr, StyleAttr): + attr = attr.value + + css = self._css + if pre: + css = self._pre_css + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Remove %s %s', selector, attr) + rule.style.removeProperty(attr) + break + self._write(pre) + + def remove_font(self, selector, pre=False): + css = self._css + if pre: + css = self._pre_css + + for rule in css: + if rule.type != rule.STYLE_RULE: + continue + if rule.selectorText == selector: + log.info('Remove Font from: %s', selector) + rule.style.removeProperty('font-family') + rule.style.removeProperty('font-size') + rule.style.removeProperty('font-style') + rule.style.removeProperty('font-weight') + break + self._write(pre) + + def change_theme(self, theme): + user = not theme == 'default' + theme_path = self.get_theme_path(theme, user=user) + if not os.path.exists(theme_path): + log.error('Change Theme: Theme %s does not exist', theme_path) + return False + self._load_selected(theme_path) + self._activate_theme() + app.config.set('roster_theme', theme) + log.info('Change Theme: Successful switched to %s', theme) + return True + + def change_preload_theme(self, theme): + theme_path = self.get_theme_path(theme) + if not os.path.exists(theme_path): + log.error('Change Preload Theme: Theme %s does not exist', + theme_path) + return False + self._load_pre(theme) + log.info('Successful switched to %s', theme) + return True + + def rename_theme(self, old_theme, new_theme): + if old_theme not in self.themes: + log.error('Rename Theme: Old theme %s not found', old_theme) + return False + + if new_theme in self.themes: + log.error('Rename Theme: New theme %s exists already', new_theme) + return False + + old_theme_path = self.get_theme_path(old_theme) + new_theme_path = self.get_theme_path(new_theme) + os.rename(old_theme_path, new_theme_path) + self.themes.remove(old_theme) + self.themes.append(new_theme) + self._load_pre(new_theme) + log.info('Rename Theme: Successful renamed theme from %s to %s', + old_theme, new_theme) + return True + + def _activate_theme(self): + log.info('Activate theme') + self._invalidate_cache() + self._provider.load_from_data(self._css.cssText) + + def add_new_theme(self, theme): + theme_path = self.get_theme_path(theme) + if os.path.exists(theme_path): + log.error('Add Theme: %s exists already', theme_path) + return False + with open(theme_path, 'w', encoding='utf8'): + pass + self.themes.append(theme) + log.info('Add Theme: Successful added theme %s', theme) + return True + + def remove_theme(self, theme): + theme_path = self.get_theme_path(theme) + if os.path.exists(theme_path): + os.remove(theme_path) + self.themes.remove(theme) + log.info('Remove Theme: Successful removed theme %s', theme) + + def _add_to_cache(self, selector, attr, value): + self._cache[selector + attr] = value + + def _get_from_cache(self, selector, attr): + return self._cache[selector + attr] + + def _invalidate_cache(self): + self._cache = {} diff --git a/gajim/conversation_textview.py b/gajim/conversation_textview.py index 7034c0062..0d3044c08 100644 --- a/gajim/conversation_textview.py +++ b/gajim/conversation_textview.py @@ -49,6 +49,7 @@ from gajim.common import i18n from calendar import timegm from gajim.common.fuzzyclock import FuzzyClock from gajim import emoticons +from gajim.common.const import StyleAttr from gajim.htmltextview import HtmlTextView @@ -216,42 +217,47 @@ class ConversationTextview(GObject.GObject): self.last_time_printout = 0 style = self.tv.get_style_context() - style.add_class('font_custom') + style.add_class('gajim-conversation-font') buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.create_mark('end', end_iter, False) self.tagIn = buffer_.create_tag('incoming') - color = app.config.get('inmsgcolor') - font = Pango.FontDescription(app.config.get('inmsgfont')) + color = app.css_config.get_value( + '.gajim-incoming-nickname', StyleAttr.COLOR) self.tagIn.set_property('foreground', color) - self.tagIn.set_property('font-desc', font) + desc = app.css_config.get_font('.gajim-incoming-nickname') + self.tagIn.set_property('font-desc', desc) self.tagOut = buffer_.create_tag('outgoing') - color = app.config.get('outmsgcolor') - font = Pango.FontDescription(app.config.get('outmsgfont')) + color = app.css_config.get_value( + '.gajim-outgoing-nickname', StyleAttr.COLOR) self.tagOut.set_property('foreground', color) - self.tagOut.set_property('font-desc', font) + desc = app.css_config.get_font('.gajim-outgoing-nickname') + self.tagOut.set_property('font-desc', desc) self.tagStatus = buffer_.create_tag('status') - color = app.config.get('statusmsgcolor') - font = Pango.FontDescription(app.config.get('satusmsgfont')) + color = app.css_config.get_value( + '.gajim-status-message', StyleAttr.COLOR) self.tagStatus.set_property('foreground', color) - self.tagStatus.set_property('font-desc', font) + desc = app.css_config.get_font('.gajim-status-message') + self.tagStatus.set_property('font-desc', desc) self.tagInText = buffer_.create_tag('incomingtxt') - color = app.config.get('inmsgtxtcolor') - font = Pango.FontDescription(app.config.get('inmsgtxtfont')) + color = app.css_config.get_value( + '.gajim-incoming-message-text', StyleAttr.COLOR) if color: self.tagInText.set_property('foreground', color) - self.tagInText.set_property('font-desc', font) + desc = app.css_config.get_font('.gajim-incoming-message-text') + self.tagInText.set_property('font-desc', desc) self.tagOutText = buffer_.create_tag('outgoingtxt') - color = app.config.get('outmsgtxtcolor') + color = app.css_config.get_value( + '.gajim-outgoing-message-text', StyleAttr.COLOR) if color: - font = Pango.FontDescription(app.config.get('outmsgtxtfont')) - self.tagOutText.set_property('foreground', color) - self.tagOutText.set_property('font-desc', font) + self.tagOutText.set_property('foreground', color) + desc = app.css_config.get_font('.gajim-outgoing-message-text') + self.tagOutText.set_property('font-desc', desc) colors = app.config.get('gc_nicknames_colors') colors = colors.split(':') @@ -261,7 +267,8 @@ class ConversationTextview(GObject.GObject): tag.set_property('foreground', color) self.tagMarked = buffer_.create_tag('marked') - color = app.config.get('markedmsgcolor') + color = app.css_config.get_value( + '.gajim-highlight-message', StyleAttr.COLOR) self.tagMarked.set_property('foreground', color) self.tagMarked.set_property('weight', Pango.Weight.BOLD) @@ -276,7 +283,7 @@ class ConversationTextview(GObject.GObject): tag.set_property('scale', 0.8333333333333) tag = buffer_.create_tag('restored_message') - color = app.config.get('restored_messages_color') + color = app.css_config.get_value('.gajim-restored-message', StyleAttr.COLOR) tag.set_property('foreground', color) self.tv.create_tags() @@ -355,13 +362,13 @@ class ConversationTextview(GObject.GObject): self.tv.destroy() def update_tags(self): - self.tagIn.set_property('foreground', app.config.get('inmsgcolor')) - self.tagOut.set_property('foreground', app.config.get('outmsgcolor')) + self.tagIn.set_property('foreground', app.css_config.get_value('.gajim-incoming-nickname', StyleAttr.COLOR)) + self.tagOut.set_property('foreground', app.css_config.get_value('.gajim-outgoing-nickname', StyleAttr.COLOR)) self.tagStatus.set_property('foreground', - app.config.get('statusmsgcolor')) + app.css_config.get_value('.gajim-status-message', StyleAttr.COLOR)) self.tagMarked.set_property('foreground', - app.config.get('markedmsgcolor')) - color = app.config.get('urlmsgcolor') + app.css_config.get_value('.gajim-highlight-message', StyleAttr.COLOR)) + color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) self.tv.tagURL.set_property('foreground', color) self.tv.tagMail.set_property('foreground', color) self.tv.tagXMPP.set_property('foreground', color) diff --git a/gajim/data/gui/chat_control.ui b/gajim/data/gui/chat_control.ui index 8cb5dca03..d0309c7ef 100644 --- a/gajim/data/gui/chat_control.ui +++ b/gajim/data/gui/chat_control.ui @@ -567,6 +567,9 @@ + False diff --git a/gajim/data/gui/gajim_themes_window.ui b/gajim/data/gui/gajim_themes_window.ui deleted file mode 100644 index e4492a883..000000000 --- a/gajim/data/gui/gajim_themes_window.ui +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - Account row - - - Group row - - - Contact row - - - Chat Banner - - - - - False - 12 - Gajim Themes Customization - False - center-on-parent - dialog - - - True - False - vertical - 12 - - - True - False - 6 - 12 - - - True - False - vertical - 6 - - - True - True - in - - - True - True - False - - - - - - - - True - True - 0 - - - - - True - False - 6 - center - - - gtk-add - True - True - True - False - True - - - - False - False - 0 - - - - - gtk-remove - True - True - True - False - True - - - - False - False - 1 - - - - - False - True - 1 - - - - - False - True - 0 - - - - - True - False - vertical - 6 - - - True - False - liststore1 - - - - - 0 - - - - - False - True - 0 - - - - - True - False - 6 - 6 - - - Text _color: - True - True - False - True - 0 - True - - - - 0 - 0 - - - - - _Background: - True - True - False - True - 0 - True - - - - 0 - 1 - - - - - Text _font: - True - True - False - True - 0 - True - - - - 0 - 2 - - - - - True - False - Font style: - True - 0 - - - 0 - 3 - - - - - True - False - True - False - - - - 2 - 0 - - - - - True - False - True - False - - - - 2 - 1 - - - - - 15 - True - False - True - False - Sans 12 - False - - - - 1 - 2 - 2 - - - - - True - True - False - Bold - - - - True - False - gtk-bold - - - - - 1 - 3 - - - - - True - True - False - Italic - - - - True - False - gtk-italic - - - - - 2 - 3 - - - - - - - - - - - False - True - 1 - - - - - 12 - True - False - - - False - True - 2 - - - - - False - True - 1 - - - - - True - False - - - False - True - 2 - - - - - True - False - 6 - 6 - - - True - False - - - 0 - 5 - 2 - - - - - True - False - Inactive - 0 - - - 0 - 1 - - - - - True - False - Composing - 0 - - - 0 - 2 - - - - - True - False - Paused - 0 - - - 0 - 3 - - - - - True - False - Gone - 0 - - - 0 - 4 - - - - - True - False - MUC -Messages - 0 - - - 0 - 6 - - - - - True - False - MUC Directed -Messages - 0 - - - 0 - 7 - - - - - True - True - False - - - - 1 - 7 - - - - - True - True - False - - - - 1 - 6 - - - - - True - True - False - - - - 1 - 4 - - - - - True - True - False - - - - 1 - 3 - - - - - True - True - False - - - - 1 - 2 - - - - - True - True - False - - - - 1 - 1 - - - - - True - False - 5 - <b>Chatstate Tab Colors</b> - True - - - 0 - 0 - 2 - - - - - False - True - 3 - - - - - False - True - 0 - - - - - True - False - 6 - end - - - gtk-close - True - True - True - False - True - - - - False - False - 0 - - - - - False - True - 1 - - - - - - diff --git a/gajim/data/gui/groupchat_control.ui b/gajim/data/gui/groupchat_control.ui index a7af4e00c..f98bcb641 100644 --- a/gajim/data/gui/groupchat_control.ui +++ b/gajim/data/gui/groupchat_control.ui @@ -159,6 +159,9 @@ + False diff --git a/gajim/data/gui/preferences_window.ui b/gajim/data/gui/preferences_window.ui index a64ed23fb..67814e62b 100644 --- a/gajim/data/gui/preferences_window.ui +++ b/gajim/data/gui/preferences_window.ui @@ -1509,86 +1509,6 @@ $T will be replaced by auto-not-available timeout vertical 12 - - True - False - 0 - none - - - True - False - 12 - 6 - 6 - 12 - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - end - Chat message - right - start - - - - 0 - 0 - - - - - 200 - True - True - False - Sans 12 - - - - 1 - 0 - - - - - Use system _default - True - True - False - start - True - True - - - - 1 - 1 - - - - - - - - - - True - False - <b>Font</b> - True - - - - - False - True - 0 - @@ -1658,11 +1578,10 @@ $T will be replaced by auto-not-available timeout - + 200 True False - 1 @@ -1721,423 +1640,6 @@ $T will be replaced by auto-not-available timeout - - True - False - 0 - none - - - True - False - start - 12 - 6 - 6 - 12 - - - True - False - end - Contact's nickname - True - center - - - - 0 - 0 - - - - - True - False - end - Contact's message - right - - - - 0 - 1 - - - - - True - False - end - _Status message - True - center - - - - 0 - 2 - - - - - True - False - end - Group chat highlight - True - right - - - - 0 - 3 - - - - - True - False - 6 - - - True - True - False - start - True - - - - False - False - 0 - - - - - True - True - True - start - rgb(0,0,0) - - - - False - False - end - 1 - - - - - 1 - 1 - - - - - True - False - - - True - True - True - start - rgb(0,0,0) - - - - False - True - end - 0 - - - - - 1 - 2 - - - - - True - False - - - - - - True - True - True - start - rgb(0,0,0) - - - - False - True - end - 1 - - - - - 1 - 0 - - - - - True - False - - - - - - True - True - True - start - rgb(0,0,0) - - - - False - True - end - 1 - - - - - 1 - 3 - - - - - True - False - end - Your nickname - True - center - - - - 3 - 0 - - - - - True - False - end - Your message - right - - - - 3 - 1 - - - - - True - False - end - _URL highlight - True - right - - - - 3 - 2 - - - - - True - False - - - - - - True - True - True - start - rgb(0,0,0) - - - - False - True - end - 1 - - - - - 4 - 0 - - - - - True - False - 6 - - - True - True - False - start - True - - - - False - False - 0 - - - - - True - True - True - start - rgb(0,0,0) - - - - False - False - end - 1 - - - - - 4 - 1 - - - - - True - False - - - - - - True - True - True - start - rgb(0,0,0) - - - - False - True - end - 1 - - - - - 4 - 2 - - - - - 6 - True - False - - - 2 - 0 - - - - - _Reset to Default Colors - True - True - False - False - end - 6 - True - - - - 0 - 4 - 5 - - - - - - - - - - - - - - - - - - - - - - True - False - <b>Chat Line Colors</b> - True - - - - - False - True - 2 - diff --git a/gajim/data/gui/service_discovery_window.ui b/gajim/data/gui/service_discovery_window.ui index 16fe77569..29379aef3 100644 --- a/gajim/data/gui/service_discovery_window.ui +++ b/gajim/data/gui/service_discovery_window.ui @@ -65,6 +65,9 @@ Agent JID - node + False diff --git a/gajim/data/gui/themes_window.ui b/gajim/data/gui/themes_window.ui new file mode 100644 index 000000000..d000d845f --- /dev/null +++ b/gajim/data/gui/themes_window.ui @@ -0,0 +1,208 @@ + + + + + + False + + + True + True + never + in + 200 + + + True + False + + + True + False + none + + + + + + + + + + + + + + + + + True + False + 6 + 6 + + + True + True + never + in + + + 150 + True + True + theme_store + + + + + + + + Themes + + + True + + + + 0 + + + + + + + + + 0 + 0 + + + + + True + False + + + True + True + True + + + + True + False + list-add-symbolic + 1 + + + + + False + False + 0 + + + + + True + False + True + True + + + + True + False + list-remove-symbolic + 1 + + + + + False + False + 1 + + + + + + 0 + 1 + + + + + True + True + True + never + in + + + True + False + + + 200 + True + False + True + none + False + + + + + + + + 1 + 0 + + + + + True + False + + + True + True + True + popover1 + + + True + False + list-add-symbolic + 1 + + + + + False + True + end + 0 + + + + + + 1 + 1 + + + + diff --git a/gajim/data/style/default-dark.css b/gajim/data/style/default-dark.css new file mode 100644 index 000000000..dabf5dc1c --- /dev/null +++ b/gajim/data/style/default-dark.css @@ -0,0 +1,18 @@ +.gajim-incoming-nickname { + color: rgb(164, 0, 0) + } +.gajim-outgoing-nickname { + color: rgb(52, 101, 164) + } +.gajim-url { + color: rgb(117, 80, 123) + } +.gajim-highlight-message { + color: rgb(245, 121, 0) + } +.gajim-msg-correcting text { + background: rgb(131, 148, 150) + } +.gajim-status-message { + color: rgb(115, 210, 22) + } \ No newline at end of file diff --git a/gajim/data/style/default.css b/gajim/data/style/default.css new file mode 100644 index 000000000..5e34b8508 --- /dev/null +++ b/gajim/data/style/default.css @@ -0,0 +1 @@ + .gajim-incoming-nickname { color: rgb(164, 0, 0) } .gajim-outgoing-nickname { color: rgb(52, 101, 164) } .gajim-outgoing-message-text { color: rgb(85, 87, 83) } .gajim-status-message { color: rgb(78, 154, 6) } .gajim-url { color: rgb(32, 74, 135) } .gajim-highlight-message { color: rgb(255, 128, 128) } .gajim-restored-message { color: rgb(85, 87, 83) } .gajim-roster-disconnected { background: rgb(171 ,97 ,97) } .gajim-roster-connected { background: rgb(173, 195, 198) } \ No newline at end of file diff --git a/gajim/data/style/gajim-dark.css b/gajim/data/style/gajim-dark.css new file mode 100644 index 000000000..e6965eb52 --- /dev/null +++ b/gajim/data/style/gajim-dark.css @@ -0,0 +1,2 @@ +/* Gajim Dark Application CSS File */ + diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index 510d4f751..7e89ed7d0 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -1,5 +1,31 @@ /* Gajim Application CSS File */ +.gajim-state-composing { + color: rgb(0, 139, 0) + } +.gajim-state-inactive { + color: rgb(158, 158, 158) + } +.gajim-state-gone { + color: rgb(128, 128, 128) + } +.gajim-state-paused { + color: rgb(0, 0, 205) + } +.gajim-msg-correcting text { + background: rgb(238, 232, 170) + } +.gajim-state-tab-muc-directed-msg { + color: rgb(238, 0, 0) + } +.gajim-state-tab-muc-msg { + color: rgb(0, 0, 205) + } +.gajim-banner { + font-size: 13pt; + font-weight: 700 + } + .chatcontrol-actionbar-button { padding: 0px 5px 0px 5px; @@ -136,6 +162,28 @@ list.settings > row > box { opacity: 0.95; } +/* ThemesWindow */ +#ThemesWindow grid { padding: 12px; } + +.theme_listbox row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; padding: 5px 10px 5px 10px;} +.theme_listbox row:last-child { border-bottom: 0px} +.theme_listbox row.activatable:active { box-shadow: none; } +.theme_listbox row:not(.activatable) label { color: @insensitive_fg_color } +.theme_listbox row:focus { outline: none; } +.theme_listbox row:hover { background-color: @theme_base_color } + +.theme_remove_button { + background-color: @theme_base_color; + border: none; + background-image: none; } + +.theme_remove_button:focus {outline: none;} + +.theme_popover_listbox { padding: 10px; } +.theme_popover_listbox row { padding: 5px; } +.theme_popover_listbox row:focus { outline: none; } + + /* Text style */ .bold16 { font-size: 16px; font-weight: bold; } diff --git a/gajim/dialogs.py b/gajim/dialogs.py index 30c1f1a92..b3392b9ed 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -1058,7 +1058,6 @@ class SynchroniseSelectContactsDialog: iter_ = model.iter_next(iter_) self.dialog.destroy() - #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), 'remove': _('remove')} diff --git a/gajim/disco.py b/gajim/disco.py index 4fe38e86f..dc4d5284a 100644 --- a/gajim/disco.py +++ b/gajim/disco.py @@ -65,6 +65,7 @@ from gajim.common import app import nbxmpp from gajim.common import helpers from gajim.common import ged +from gajim.common.const import StyleAttr LABELS = { 1: _('This service has not yet responded with detailed information'), @@ -597,34 +598,10 @@ _('Without a connection, you can not browse available services')) self.banner_icon.clear() self.banner_icon.hide() # Just clearing it doesn't work - def _set_window_banner_text(self, text, text_after = None): - theme = app.config.get('roster_theme') - bannerfont = app.config.get_per('themes', theme, 'bannerfont') - bannerfontattrs = app.config.get_per('themes', theme, - 'bannerfontattrs') - - if bannerfont: - font = Pango.FontDescription(bannerfont) - else: - font = Pango.FontDescription('Normal') - if bannerfontattrs: - # B is attribute set by default - if 'B' in bannerfontattrs: - font.set_weight(Pango.Weight.HEAVY) - if 'I' in bannerfontattrs: - font.set_style(Pango.Style.ITALIC) - - font_attrs = 'font_desc="%s"' % font.to_string() - font_size = font.get_size() - - # in case there is no font specified we use x-large font size - if font_size == 0: - font_attrs = '%s size="large"' % font_attrs - markup = '%s' % (font_attrs, text) - if text_after: - font.set_weight(Pango.Weight.NORMAL) - markup = '%s\n%s' % \ - (markup, font.to_string(), text_after) + def _set_window_banner_text(self, text, text_after=None): + markup = '%s' % (text) + if text_after is not None: + markup += '\n%s' % text_after self.banner.set_markup(markup) def destroy(self, chain = False): @@ -1195,8 +1172,7 @@ class ToplevelAgentBrowser(AgentBrowser): # Normal/success cell.set_property('foreground_set', False) else: - theme = app.config.get('roster_theme') - bgcolor = app.config.get_per('themes', theme, 'groupbgcolor') + bgcolor = app.css_config.get_value('.gajim-group-row', StyleAttr.BACKGROUND) if bgcolor: cell.set_property('cell_background_set', True) cell.set_property('foreground_set', False) @@ -1329,8 +1305,7 @@ class ToplevelAgentBrowser(AgentBrowser): AgentBrowser.cleanup(self) def update_theme(self): - theme = app.config.get('roster_theme') - bgcolor = app.config.get_per('themes', theme, 'groupbgcolor') + bgcolor = app.css_config.get_value('.gajim-group-row', StyleAttr.BACKGROUND) if bgcolor: self._renderer.set_property('cell-background', bgcolor) self.window.services_treeview.queue_draw() diff --git a/gajim/gajim_themes_window.py b/gajim/gajim_themes_window.py deleted file mode 100644 index f39fbff43..000000000 --- a/gajim/gajim_themes_window.py +++ /dev/null @@ -1,411 +0,0 @@ -# -*- coding:utf-8 -*- -## src/gajim_themes_window.py -## -## Copyright (C) 2003-2014 Yann Leboulanger -## Copyright (C) 2005-2006 Dimitur Kirov -## Nikos Kouremenos -## Copyright (C) 2006 Jean-Marie Traissard -## Copyright (C) 2007 Stephan Erb -## -## 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 Gtk -from gi.repository import Gdk -from gi.repository import Pango - -from gajim.common import app - -from gajim.gtk import ErrorDialog -from gajim.gtk.util import get_builder - - -class GajimThemesWindow: - - def __init__(self, transient): - self.xml = get_builder('gajim_themes_window.ui') - self.window = self.xml.get_object('gajim_themes_window') - self.window.set_transient_for(transient) - - self.options = ['account', 'group', 'contact', 'banner'] - self.options_combobox = self.xml.get_object('options_combobox') - self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton') - self.background_checkbutton = self.xml.get_object('background_checkbutton') - self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton') - self.text_colorbutton = self.xml.get_object('text_colorbutton') - self.background_colorbutton = self.xml.get_object('background_colorbutton') - self.text_fontbutton = self.xml.get_object('text_fontbutton') - self.bold_togglebutton = self.xml.get_object('bold_togglebutton') - self.italic_togglebutton = self.xml.get_object('italic_togglebutton') - self.themes_tree = self.xml.get_object('themes_treeview') - self.theme_options_vbox = self.xml.get_object('theme_options_vbox') - self.theme_options_table = self.xml.get_object('theme_options_table') - self.colorbuttons = {} - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \ - '_colorbutton') - model = Gtk.ListStore(str) - self.themes_tree.set_model(model) - col = Gtk.TreeViewColumn(_('Theme')) - self.themes_tree.append_column(col) - renderer = Gtk.CellRendererText() - col.pack_start(renderer, True) - col.add_attribute(renderer, 'text', 0) - renderer.connect('edited', self.on_theme_cell_edited) - renderer.set_property('editable', True) - self.current_theme = app.config.get('roster_theme') - self.no_update = False - self.fill_themes_treeview() - self.select_active_theme() - self.current_option = self.options[0] - self.set_theme_options(self.current_theme, self.current_option) - - self.xml.connect_signals(self) - self.window.connect('delete-event', self.on_themese_window_delete_event) - self.themes_tree.get_selection().connect('changed', - self.selection_changed) - self.window.show_all() - - def on_themese_window_delete_event(self, widget, event): - self.window.hide() - return True # do NOT destroy the window - - def on_close_button_clicked(self, widget): - window = app.get_app_window('Preferences') - if window is not None: - window.update_theme_list() - self.window.hide() - - def on_theme_cell_edited(self, cell, row, new_name): - model = self.themes_tree.get_model() - iter_ = model.get_iter_from_string(row) - old_name = model.get_value(iter_, 0) - if old_name == new_name: - return - if old_name == 'default': - ErrorDialog( - _('You cannot make changes to the default theme'), - _('Please create a new clean theme.')) - return - new_config_name = new_name.replace(' ', '_') - if new_config_name in app.config.get_per('themes'): - return - app.config.add_per('themes', new_config_name) - # Copy old theme values - old_config_name = old_name.replace(' ', '_') - properties = ['textcolor', 'bgcolor', 'font', 'fontattrs'] - app.config.add_per('themes', new_config_name) - for option in self.options: - for property_ in properties: - option_name = option + property_ - app.config.set_per('themes', new_config_name, option_name, - app.config.get_per('themes', old_config_name, option_name)) - app.config.del_per('themes', old_config_name) - if old_config_name == app.config.get('roster_theme'): - app.config.set('roster_theme', new_config_name) - model.set_value(iter_, 0, new_name) - self.current_theme = new_name - - def fill_themes_treeview(self): - model = self.themes_tree.get_model() - model.clear() - for config_theme in app.config.get_per('themes'): - theme = config_theme.replace('_', ' ') - model.append([theme]) - - def select_active_theme(self): - model = self.themes_tree.get_model() - iter_ = model.get_iter_first() - active_theme = app.config.get('roster_theme').replace('_', ' ') - while iter_: - theme = model[iter_][0] - if theme == active_theme: - self.themes_tree.get_selection().select_iter(iter_) - if active_theme == 'default': - self.xml.get_object('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_object('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) - break - iter_ = model.iter_next(iter_) - - def selection_changed(self, widget = None): - (model, iter_) = self.themes_tree.get_selection().get_selected() - selected = self.themes_tree.get_selection().get_selected_rows() - if not iter_ or selected[1] == []: - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - return - self.current_theme = model.get_value(iter_, 0) - self.current_theme = self.current_theme.replace(' ', '_') - self.set_theme_options(self.current_theme) - if self.current_theme == 'default': - self.xml.get_object('remove_button').set_sensitive(False) - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - else: - self.xml.get_object('remove_button').set_sensitive(True) - self.theme_options_vbox.set_sensitive(True) - self.theme_options_table.set_sensitive(True) - - def on_add_button_clicked(self, widget): - model = self.themes_tree.get_model() - iter_ = model.append() - i = 0 - # don't confuse translators - theme_name = _('theme name') - theme_name_ns = theme_name.replace(' ', '_') - while theme_name_ns + str(i) in app.config.get_per('themes'): - i += 1 - model.set_value(iter_, 0, theme_name + str(i)) - app.config.add_per('themes', theme_name_ns + str(i)) - self.themes_tree.get_selection().select_iter(iter_) - col = self.themes_tree.get_column(0) - path = model.get_path(iter_) - self.themes_tree.set_cursor(path, col, True) - - def on_remove_button_clicked(self, widget): - (model, iter_) = self.themes_tree.get_selection().get_selected() - if not iter_: - return - if self.current_theme == app.config.get('roster_theme'): - ErrorDialog( - _('You cannot delete your current theme'), - _('Pick another theme to use first.')) - return - self.theme_options_vbox.set_sensitive(False) - self.theme_options_table.set_sensitive(False) - self.xml.get_object('remove_button').set_sensitive(False) - app.config.del_per('themes', self.current_theme) - model.remove(iter_) - - def set_theme_options(self, theme, option = 'account'): - self.no_update = True - self.options_combobox.set_active(self.options.index(option)) - textcolor = app.config.get_per('themes', theme, option + 'textcolor') - if textcolor: - state = True - rgba = Gdk.RGBA() - rgba.parse(textcolor) - self.text_colorbutton.set_rgba(rgba) - else: - state = False - self.textcolor_checkbutton.set_active(state) - self.text_colorbutton.set_sensitive(state) - bgcolor = app.config.get_per('themes', theme, option + 'bgcolor') - if bgcolor: - state = True - rgba = Gdk.RGBA() - rgba.parse(bgcolor) - self.background_colorbutton.set_rgba(rgba) - else: - state = False - self.background_checkbutton.set_active(state) - self.background_colorbutton.set_sensitive(state) - - # get the font name before we set widgets and it will not be overridden - font_name = app.config.get_per('themes', theme, option + 'font') - font_attrs = app.config.get_per('themes', theme, option + 'fontattrs') - self._set_font_widgets(font_attrs) - if font_name: - state = True - self.text_fontbutton.set_font_name(font_name) - else: - state = False - self.textfont_checkbutton.set_active(state) - self.text_fontbutton.set_sensitive(state) - self.no_update = False - app.interface.roster.change_roster_style(None) - - for chatstate in ('inactive', 'composing', 'paused', 'gone', - 'muc_msg', 'muc_directed_msg'): - color = app.config.get_per('themes', theme, 'state_' + chatstate + \ - '_color') - rgba = Gdk.RGBA() - rgba.parse(color) - self.colorbuttons[chatstate].set_rgba(rgba) - - def on_textcolor_checkbutton_toggled(self, widget): - state = widget.get_active() - self.text_colorbutton.set_sensitive(state) - self._set_color(state, self.text_colorbutton, - 'textcolor') - - def on_background_checkbutton_toggled(self, widget): - state = widget.get_active() - self.background_colorbutton.set_sensitive(state) - self._set_color(state, self.background_colorbutton, - 'bgcolor') - - def on_textfont_checkbutton_toggled(self, widget): - self.text_fontbutton.set_sensitive(widget.get_active()) - self._set_font() - - def on_text_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'textcolor') - - def on_background_colorbutton_color_set(self, widget): - self._set_color(True, widget, 'bgcolor') - - def on_text_fontbutton_font_set(self, widget): - self._set_font() - - def on_options_combobox_changed(self, widget): - index = self.options_combobox.get_active() - if index == -1: - return - self.current_option = self.options[index] - self.set_theme_options(self.current_theme, - self.current_option) - - def on_bold_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() - - def on_italic_togglebutton_toggled(self, widget): - if not self.no_update: - self._set_font() - - def _set_color(self, state, widget, option): - """ - Set color value in prefs and update the UI - """ - if state: - color = widget.get_rgba() - color_string = color.to_string() - else: - color_string = '' - begin_option = '' - if not option.startswith('state'): - begin_option = self.current_option - app.config.set_per('themes', self.current_theme, - begin_option + option, color_string) - # use faster functions for this - if self.current_option == 'banner': - app.interface.roster.repaint_themed_widgets() - return - if self.no_update: - return - app.interface.roster.change_roster_style(self.current_option) - - def _set_font(self): - """ - Set font value in prefs and update the UI - """ - state = self.textfont_checkbutton.get_active() - if state: - font_string = self.text_fontbutton.get_font_name() - else: - font_string = '' - app.config.set_per('themes', self.current_theme, - self.current_option + 'font', font_string) - font_attrs = self._get_font_attrs() - app.config.set_per('themes', self.current_theme, - self.current_option + 'fontattrs', font_attrs) - # use faster functions for this - if self.current_option == 'banner': - app.interface.roster.repaint_themed_widgets() - if self.no_update: - return - app.interface.roster.change_roster_style(self.current_option) - - def _toggle_font_widgets(self, font_props): - """ - Toggle font buttons with the bool values of font_props tuple - """ - self.bold_togglebutton.set_active(font_props[0]) - self.italic_togglebutton.set_active(font_props[1]) - - def _get_font_description(self): - """ - Return a FontDescription from togglebuttons states - """ - fd = Pango.FontDescription() - if self.bold_togglebutton.get_active(): - fd.set_weight(Pango.Weight.BOLD) - if self.italic_togglebutton.get_active(): - fd.set_style(Pango.Style.ITALIC) - return fd - - def _set_font_widgets(self, font_attrs): - """ - Set the correct toggle state of font style buttons by a font string of - type 'BI' - """ - font_props = [False, False, False] - if font_attrs: - if font_attrs.find('B') != -1: - font_props[0] = True - if font_attrs.find('I') != -1: - font_props[1] = True - self._toggle_font_widgets(font_props) - - def _get_font_attrs(self): - """ - Get a string with letters of font attribures: 'BI' - """ - attrs = '' - if self.bold_togglebutton.get_active(): - attrs += 'B' - if self.italic_togglebutton.get_active(): - attrs += 'I' - return attrs - - - def _get_font_props(self, font_name): - """ - Get tuple of font properties: weight, style - """ - font_props = [False, False, False] - font_description = Pango.FontDescription(font_name) - if font_description.get_weight() != Pango.Weight.NORMAL: - font_props[0] = True - if font_description.get_style() != Pango.Style.ITALIC: - font_props[1] = True - return font_props - - def on_inactive_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_inactive_color') - self.no_update = False - - def on_composing_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_composing_color') - self.no_update = False - - def on_paused_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_paused_color') - self.no_update = False - - def on_gone_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_gone_color') - self.no_update = False - - def on_muc_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_msg_color') - self.no_update = False - - def on_muc_directed_msg_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_muc_directed_msg_color') - self.no_update = False diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index ef442edf6..3cb5f8a14 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -59,6 +59,7 @@ from gajim.common.modules import dataforms from gajim.common import ged from gajim.common import i18n from gajim.common import contacts +from gajim.common.const import StyleAttr from gajim.chat_control import ChatControl from gajim.chat_control_base import ChatControlBase @@ -95,11 +96,11 @@ def cell_data_func(column, renderer, model, iter_, user_data): theme = app.config.get('roster_theme') has_parent = bool(model.iter_parent(iter_)) if has_parent: - bgcolor = app.config.get_per('themes', theme, 'contactbgcolor') - renderer.set_property('cell-background', bgcolor or None) + bgcolor = app.css_config.get_value('.gajim-contact-row', StyleAttr.BACKGROUND) + renderer.set_property('cell-background', bgcolor) else: - bgcolor = app.config.get_per('themes', theme, 'groupbgcolor') - renderer.set_property('cell-background', bgcolor or None) + bgcolor = app.css_config.get_value('.gajim-group-row', StyleAttr.BACKGROUND) + renderer.set_property('cell-background', bgcolor) if user_data == 'status': status_cell_data_func(column, renderer, model, iter_, has_parent) @@ -133,18 +134,15 @@ def text_cell_data_func(column, renderer, model, iter_, has_parent, theme): # cell data func is global, because we don't want it to keep # reference to GroupchatControl instance (self) if has_parent: - color = app.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + color = app.css_config.get_value('.gajim-contact-row', StyleAttr.COLOR) + renderer.set_property('foreground', color) + desc = app.css_config.get_font('.gajim-contact-row') + renderer.set_property('font-desc', desc) else: - color = app.config.get_per('themes', theme, 'grouptextcolor') - renderer.set_property('foreground', color or None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + color = app.css_config.get_value('.gajim-group-row', StyleAttr.COLOR) + renderer.set_property('foreground', color) + desc = app.css_config.get_font('.gajim-group-row') + renderer.set_property('font-desc', desc) class PrivateChatControl(ChatControl): @@ -1023,14 +1021,14 @@ class GroupchatControl(ChatControlBase): color = None if chatstate == 'attention' and (not has_focus or not current_tab): self.attention_flag = True - color = 'state_muc_directed_msg_color' + color = 'tab-muc-directed-msg' elif chatstate == 'active' or (current_tab and has_focus): self.attention_flag = False # get active color from gtk color = 'active' elif chatstate == 'newmsg' and (not has_focus or not current_tab) \ and not self.attention_flag: - color = 'state_muc_msg_color' + color = 'tab-muc-msg' if self.is_continued: # if this is a continued conversation @@ -1146,18 +1144,16 @@ class GroupchatControl(ChatControlBase): room jid, subject """ self.name_label.set_ellipsize(Pango.EllipsizeMode.END) - font_attrs, font_attrs_small = self.get_font_attrs() if self.is_continued: name = self.get_continued_conversation_name() else: name = self.room_jid - text = '%s' % (font_attrs, name) - self.name_label.set_markup(text) + + self.name_label.set_text(name) if self.subject: subject = GLib.markup_escape_text(self.subject) subject_text = self.urlfinder.sub(self.make_href, subject) - subject_text = '%s' % subject_text self.subject_button.get_popover().set_text(subject_text) def _nec_vcard_published(self, obj): @@ -1719,13 +1715,8 @@ class GroupchatControl(ChatControlBase): if status != '': status = helpers.reduce_chars_newlines(status, max_lines=1) # escape markup entities and make them small italic and fg color - color = gtkgui_helpers.get_fade_color(self.list_treeview, - selected, focus) - colorstring = "#%04x%04x%04x" % (int(color.red * 65535), - int(color.green * 65535), int(color.blue * 65535)) - name += ('\n' - '%s') % (colorstring, GLib.markup_escape_text( - status)) + name += ('\n' + '{}'.format(GLib.markup_escape_text(status))) if (gc_contact.affiliation != 'none' and app.config.get('show_affiliation_in_groupchat')): @@ -2112,7 +2103,7 @@ class GroupchatControl(ChatControlBase): if self.correcting: self.correcting = False gtkgui_helpers.remove_css_class( - self.msg_textview, 'msgcorrectingcolor') + self.msg_textview, 'gajim-msg-correcting') def send_message(self, message, xhtml=None, process_commands=True): """ diff --git a/gajim/gtk/__init__.py b/gajim/gtk/__init__.py index f26941488..6b9444a80 100644 --- a/gajim/gtk/__init__.py +++ b/gajim/gtk/__init__.py @@ -52,6 +52,7 @@ from gajim.gtk.dialogs import AspellDictError from gajim.gtk.dialogs import HigDialog from gajim.gtk.dialogs import SSLErrorDialog from gajim.gtk.dialogs import ChangePasswordDialog +from gajim.gtk.dialogs import NewConfirmationDialog from gajim.gtk.about import AboutDialog from gajim.gtk.join_groupchat import JoinGroupchatWindow diff --git a/gajim/gtk/dialogs.py b/gajim/gtk/dialogs.py index 62b693ae6..fb31e8c27 100644 --- a/gajim/gtk/dialogs.py +++ b/gajim/gtk/dialogs.py @@ -986,3 +986,42 @@ class ChangePasswordDialog(Gtk.Dialog): self._error_label.set_text(error_text) self._password1_entry.set_sensitive(True) self._password2_entry.set_sensitive(True) + + +class NewConfirmationDialog(Gtk.MessageDialog): + def __init__(self, text, sec_text, buttons, transient_for=None): + Gtk.MessageDialog.__init__(self, + transient_for=transient_for, + message_type=Gtk.MessageType.QUESTION, + text=text) + + self._buttons = buttons + + for response, button in buttons.items(): + self.add_button(button.text, response) + if button.action is not None: + widget = self.get_widget_for_response(response) + widget.get_style_context().add_class(button.action.value) + + self.format_secondary_markup(sec_text) + + self.connect('response', self._on_response) + + self.run() + + def _on_response(self, dialog, response): + if response == Gtk.ResponseType.DELETE_EVENT: + # Look if DELETE_EVENT is mapped to another response + response = self._buttons.get(response, None) + if response is None: + # If DELETE_EVENT was not mapped we assume CANCEL + response = Gtk.ResponseType.CANCEL + + button = self._buttons.get(response, None) + if button is None: + self.destroy() + return + + if button.callback is not None: + button.callback() + self.destroy() diff --git a/gajim/gtk/preferences.py b/gajim/gtk/preferences.py index f65c26b3f..1b90885d9 100644 --- a/gajim/gtk/preferences.py +++ b/gajim/gtk/preferences.py @@ -25,7 +25,7 @@ from gajim.common import config as c_config from gajim.common import idle from gajim.gtk.util import get_builder from gajim.gtk import AspellDictError -from gajim.gajim_themes_window import GajimThemesWindow +from gajim.gtk.themes import Themes from gajim.advanced_configuration_window import AdvancedConfigurationWindow from gajim.chat_control_base import ChatControlBase from gajim.config import ManageProxiesWindow, ManageSoundsWindow @@ -160,9 +160,8 @@ class Preferences(Gtk.ApplicationWindow): ### Style tab ### # Themes theme_combobox = self.xml.get_object('theme_combobox') - cell = Gtk.CellRendererText() - theme_combobox.pack_start(cell, True) - theme_combobox.add_attribute(cell, 'text', 0) + self.changed_id = theme_combobox.connect( + 'changed', self.on_theme_combobox_changed) self.update_theme_list() # iconset @@ -207,19 +206,6 @@ class Preferences(Gtk.ApplicationWindow): st = app.config.get('use_transports_iconsets') self.xml.get_object('transports_iconsets_checkbutton').set_active(st) - # Color widgets - self.draw_color_widgets() - - # Font for messages - font = app.config.get('conversation_font') - # try to set default font for the current desktop env - fontbutton = self.xml.get_object('conversation_fontbutton') - if font == '': - fontbutton.set_sensitive(False) - self.xml.get_object('default_chat_font').set_active(True) - else: - fontbutton.set_font_name(font) - ### Personal Events tab ### # outgoing send chat state notifications st = app.config.get('outgoing_chat_state_notifications') @@ -473,8 +459,8 @@ class Preferences(Gtk.ApplicationWindow): self.default_msg_tree.get_model().connect('row-changed', self.on_default_msg_treemodel_row_changed) - self.theme_preferences = None self.sounds_preferences = None + self.theme_preferences = None self.notebook.set_current_page(0) @@ -625,36 +611,32 @@ class Preferences(Gtk.ApplicationWindow): def on_show_avatar_in_tabs_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'show_avatar_in_tabs') - def on_theme_combobox_changed(self, widget): - model = widget.get_model() - active = widget.get_active() - config_theme = model[active][0].replace(' ', '_') - - app.config.set('roster_theme', config_theme) + @staticmethod + def on_theme_combobox_changed(combobox): + theme = combobox.get_active_id() + app.config.set('roster_theme', theme) + app.css_config.change_theme(theme) # begin repainting themed widgets throughout app.interface.roster.repaint_themed_widgets() app.interface.roster.change_roster_style(None) - gtkgui_helpers.load_css() def update_theme_list(self): theme_combobox = self.xml.get_object('theme_combobox') - model = Gtk.ListStore(str) - theme_combobox.set_model(model) - i = 0 - for config_theme in app.config.get_per('themes'): - theme = config_theme.replace('_', ' ') - model.append([theme]) - if app.config.get('roster_theme') == config_theme: - theme_combobox.set_active(i) - i += 1 + with theme_combobox.handler_block(self.changed_id): + theme_combobox.remove_all() + theme_combobox.append('default', 'default') + for config_theme in app.css_config.themes: + theme_combobox.append(config_theme, config_theme) + + theme_combobox.set_active_id(app.config.get('roster_theme')) def on_manage_theme_button_clicked(self, widget): - if self.theme_preferences is None: - self.theme_preferences = GajimThemesWindow(self) + window = app.get_app_window(Themes) + if window is None: + Themes(self) else: - self.theme_preferences.window.present() - self.theme_preferences.select_active_theme() + window.present() def on_iconset_combobox_changed(self, widget): model = widget.get_model() @@ -747,128 +729,6 @@ class Preferences(Gtk.ApplicationWindow): else: self.sounds_preferences.window.present() - def update_text_tags(self): - """ - Update color tags in opened chat windows - """ - for ctrl in self._get_all_controls(): - ctrl.update_tags() - - def on_preference_widget_color_set(self, widget, text): - color = widget.get_color() - color_string = color.to_string() - app.config.set(text, color_string) - self.update_text_tags() - - def on_preference_widget_font_set(self, widget, text): - if widget: - font = widget.get_font_name() - else: - font = '' - app.config.set(text, font) - gtkgui_helpers.load_css() - - def on_incoming_nick_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'inmsgcolor') - - def on_outgoing_nick_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'outmsgcolor') - - def on_incoming_msg_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'inmsgtxtcolor') - - def on_outgoing_msg_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'outmsgtxtcolor') - - def on_url_msg_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'urlmsgcolor') - - def on_status_msg_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'statusmsgcolor') - - def on_muc_highlight_colorbutton_color_set(self, widget): - self.on_preference_widget_color_set(widget, 'markedmsgcolor') - - def on_conversation_fontbutton_font_set(self, widget): - self.on_preference_widget_font_set(widget, 'conversation_font') - - def on_default_chat_font_toggled(self, widget): - font_widget = self.xml.get_object('conversation_fontbutton') - if widget.get_active(): - font_widget.set_sensitive(False) - font_widget = None - else: - font_widget.set_sensitive(True) - self.on_preference_widget_font_set(font_widget, 'conversation_font') - - def draw_color_widgets(self): - col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton', - 'outmsgcolor': 'outgoing_nick_colorbutton', - 'inmsgtxtcolor': ['incoming_msg_colorbutton', - 'incoming_msg_checkbutton'], - 'outmsgtxtcolor': ['outgoing_msg_colorbutton', - 'outgoing_msg_checkbutton'], - 'statusmsgcolor': 'status_msg_colorbutton', - 'urlmsgcolor': 'url_msg_colorbutton', - 'markedmsgcolor': 'muc_highlight_colorbutton'} - for c in col_to_widget: - col = app.config.get(c) - if col: - if isinstance(col_to_widget[c], list): - rgba = Gdk.RGBA() - rgba.parse(col) - self.xml.get_object(col_to_widget[c][0]).set_rgba(rgba) - self.xml.get_object(col_to_widget[c][0]).set_sensitive(True) - self.xml.get_object(col_to_widget[c][1]).set_active(True) - else: - rgba = Gdk.RGBA() - rgba.parse(col) - self.xml.get_object(col_to_widget[c]).set_rgba(rgba) - else: - rgba = Gdk.RGBA() - rgba.parse('#000000') - if isinstance(col_to_widget[c], list): - self.xml.get_object(col_to_widget[c][0]).set_rgba(rgba) - self.xml.get_object(col_to_widget[c][0]).set_sensitive(False) - self.xml.get_object(col_to_widget[c][1]).set_active(False) - else: - self.xml.get_object(col_to_widget[c]).set_rgba(rgba) - - def on_reset_colors_button_clicked(self, widget): - col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton', - 'outmsgcolor': 'outgoing_nick_colorbutton', - 'inmsgtxtcolor': 'incoming_msg_colorbutton', - 'outmsgtxtcolor': 'outgoing_msg_colorbutton', - 'statusmsgcolor': 'status_msg_colorbutton', - 'urlmsgcolor': 'url_msg_colorbutton', - 'markedmsgcolor': 'muc_highlight_colorbutton'} - for c in col_to_widget: - app.config.set(c, app.interface.default_colors[c]) - self.draw_color_widgets() - - self.update_text_tags() - - def _set_color(self, state, widget_name, option): - """ - Set color value in prefs and update the UI - """ - if state: - color = self.xml.get_object(widget_name).get_rgba() - color_string = color.to_string() - else: - color_string = '' - app.config.set(option, color_string) - - def on_incoming_msg_checkbutton_toggled(self, widget): - state = widget.get_active() - self.xml.get_object('incoming_msg_colorbutton').set_sensitive(state) - self._set_color(state, 'incoming_msg_colorbutton', 'inmsgtxtcolor') - - def on_outgoing_msg_checkbutton_toggled(self, widget): - state = widget.get_active() - self.xml.get_object('outgoing_msg_colorbutton').set_sensitive(state) - self._set_color(state, 'outgoing_msg_colorbutton', 'outmsgtxtcolor') - def on_auto_away_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'autoaway', [self.auto_away_time_spinbutton, self.auto_away_message_entry]) diff --git a/gajim/gtk/themes.py b/gajim/gtk/themes.py new file mode 100644 index 000000000..0c86ec485 --- /dev/null +++ b/gajim/gtk/themes.py @@ -0,0 +1,415 @@ +# Copyright (C) 2018 Philipp Hörist +# +# 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 collections import namedtuple +from enum import IntEnum + +from gi.repository import Gtk +from gi.repository import Gdk + +from gajim.common import app +from gajim.common.const import StyleAttr, DialogButton, ButtonAction +from gajim.common.connection_handlers_events import StyleChanged +from gajim.gtk import ErrorDialog +from gajim.gtk import NewConfirmationDialog +from gajim.gtk.util import get_builder + +StyleOption = namedtuple('StyleOption', 'label selector attr') + +CSS_STYLE_OPTIONS = [ + StyleOption(_('Chatstate Composing'), + '.gajim-state-composing', + StyleAttr.COLOR), + + StyleOption(_('Chatstate Inactive'), + '.gajim-state-inactive', + StyleAttr.COLOR), + + StyleOption(_('Chatstate Gone'), + '.gajim-state-gone', + StyleAttr.COLOR), + + StyleOption(_('Chatstate Paused'), + '.gajim-state-paused', + StyleAttr.COLOR), + + StyleOption(_('MUC Tab New Directed Message'), + '.gajim-state-tab-muc-directed-msg', + StyleAttr.COLOR), + + StyleOption(_('MUC Tab New Message'), + '.gajim-state-tab-muc-msg', + StyleAttr.COLOR), + + StyleOption(_('Banner Foreground Color'), + '.gajim-banner', + StyleAttr.COLOR), + + StyleOption(_('Banner Background Color'), + '.gajim-banner', + StyleAttr.BACKGROUND), + + StyleOption(_('Banner Font'), + '.gajim-banner', + StyleAttr.FONT), + + StyleOption(_('Account Row Foreground Color'), + '.gajim-account-row', + StyleAttr.COLOR), + + StyleOption(_('Account Row Background Color'), + '.gajim-account-row', + StyleAttr.BACKGROUND), + + StyleOption(_('Account Row Font Color'), + '.gajim-account-row', + StyleAttr.FONT), + + StyleOption(_('Group Row Foreground Color'), + '.gajim-group-row', + StyleAttr.COLOR), + + StyleOption(_('Group Row Background Color'), + '.gajim-group-row', + StyleAttr.BACKGROUND), + + StyleOption(_('Group Row Font Color'), + '.gajim-group-row', + StyleAttr.FONT), + + StyleOption(_('Contact Row Foreground Color'), + '.gajim-contact-row', + StyleAttr.COLOR), + + StyleOption(_('Contact Row Background Color'), + '.gajim-contact-row', + StyleAttr.BACKGROUND), + + StyleOption(_('Contact Row Font Color'), + '.gajim-contact-row', + StyleAttr.FONT), + + StyleOption(_('Conversation Font'), + '.gajim-conversation-font', + StyleAttr.FONT), + + StyleOption(_('Incoming Nickname Color'), + '.gajim-incoming-nickname', + StyleAttr.COLOR), + + StyleOption(_('Outgoing Nickname Color'), + '.gajim-outgoing-nickname', + StyleAttr.COLOR), + + StyleOption(_('Incoming Message Text Color'), + '.gajim-incoming-message-text', + StyleAttr.COLOR), + + StyleOption(_('Incoming Message Text Font'), + '.gajim-incoming-message-text', + StyleAttr.FONT), + + StyleOption(_('Outgoing Message Text Color'), + '.gajim-outgoing-message-text', + StyleAttr.COLOR), + + StyleOption(_('Outgoing Message Text Font'), + '.gajim-outgoing-message-text', + StyleAttr.FONT), + + StyleOption(_('Status Message Color'), + '.gajim-status-message', + StyleAttr.COLOR), + + StyleOption(_('Status Message Font'), + '.gajim-status-message', + StyleAttr.FONT), + + StyleOption(_('URL Color'), + '.gajim-url', + StyleAttr.COLOR), + + StyleOption(_('Highlight Message Color'), + '.gajim-highlight-message', + StyleAttr.COLOR), + + StyleOption(_('Message Correcting'), + '.gajim-msg-correcting text', + StyleAttr.BACKGROUND), + + StyleOption(_('Restored Message Color'), + '.gajim-restored-message', + StyleAttr.COLOR), + + StyleOption(_('Contact Disconnected Background'), + '.gajim-roster-disconnected', + StyleAttr.BACKGROUND), + + StyleOption(_('Contact Connected Background '), + '.gajim-roster-connected', + StyleAttr.BACKGROUND), +] + + +class Column(IntEnum): + THEME = 0 + + +class Themes(Gtk.ApplicationWindow): + def __init__(self, transient): + Gtk.Window.__init__(self) + self.set_application(app.app) + self.set_title(_('Gajim Themes')) + self.set_name('ThemesWindow') + self.set_show_menubar(False) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_transient_for(transient) + self.set_resizable(True) + self.set_default_size(600, 400) + + self.builder = get_builder('themes_window.ui') + self.add(self.builder.get_object('theme_grid')) + + widgets = ['option_listbox', 'remove_theme_button', 'theme_store', + 'theme_treeview', 'choose_option_listbox', + 'add_option_button'] + for widget in widgets: + setattr(self, '_%s' % widget, self.builder.get_object(widget)) + + # Doesnt work if we set it in Glade + self._add_option_button.set_sensitive(False) + + self._get_themes() + + self.builder.connect_signals(self) + self.connect('destroy', self._on_destroy) + self.show_all() + + self._fill_choose_listbox() + + def _get_themes(self): + for theme in app.css_config.themes: + self._theme_store.append([theme]) + + def _on_theme_name_edit(self, renderer, path, new_name): + iter_ = self._theme_store.get_iter(path) + old_name = self._theme_store[iter_][Column.THEME] + + if new_name == 'default': + ErrorDialog( + _('Invalid Name'), + _('Name default is not allowed'), + transient_for=self) + return + + if ' ' in new_name: + ErrorDialog( + _('Invalid Name'), + _('Spaces are not allowed'), + transient_for=self) + return + + if new_name == '': + return + + result = app.css_config.rename_theme(old_name, new_name) + if result is False: + return + + self._theme_store.set_value(iter_, Column.THEME, new_name) + + def _select_theme_row(self, iter_): + self._theme_treeview.get_selection().select_iter(iter_) + + def _on_theme_selected(self, tree_selection): + store, iter_ = tree_selection.get_selected() + if iter_ is None: + self._clear_options() + return + theme = store[iter_][Column.THEME] + app.css_config.change_preload_theme(theme) + + self._add_option_button.set_sensitive(True) + self._remove_theme_button.set_sensitive(True) + self._load_options(theme) + + def _load_options(self, name): + self._option_listbox.foreach(self._remove_option) + for option in CSS_STYLE_OPTIONS: + value = app.css_config.get_value( + option.selector, option.attr, pre=True) + + if value is None: + continue + + row = Option(option, value) + self._option_listbox.add(row) + + def _add_option(self, listbox, row): + for option in self._option_listbox.get_children(): + if option == row: + return + row = Option(row.option, None) + self._option_listbox.add(row) + + def _clear_options(self): + self._option_listbox.foreach(self._remove_option) + + def _fill_choose_listbox(self): + for option in CSS_STYLE_OPTIONS: + self._choose_option_listbox.add(ChooseOption(option)) + + def _remove_option(self, row): + self._option_listbox.remove(row) + row.destroy() + + def _on_add_new_theme(self, *args): + name = self._create_theme_name() + if not app.css_config.add_new_theme(name): + return + + self._remove_theme_button.set_sensitive(True) + iter_ = self._theme_store.append([name]) + self._select_theme_row(iter_) + + @staticmethod + def _create_theme_name(): + i = 0 + while 'newtheme%s' % i in app.css_config.themes: + i += 1 + return 'newtheme%s' % i + + def _on_remove_theme(self, *args): + store, iter_ = self._theme_treeview.get_selection().get_selected() + if iter_ is None: + return + + theme = store[iter_][Column.THEME] + if theme == app.config.get('roster_theme'): + ErrorDialog( + _('Active Theme'), + _('You tried to delete the currently active theme. ' + 'Please switch to a different theme first.'), + transient_for=self) + return + + def _remove_theme(): + app.css_config.remove_theme(theme) + store.remove(iter_) + + first = store.get_iter_first() + if first is None: + self._remove_theme_button.set_sensitive(False) + self._add_option_button.set_sensitive(False) + self._clear_options() + + buttons = { + Gtk.ResponseType.CANCEL: DialogButton('Keep Theme'), + Gtk.ResponseType.OK: DialogButton('Delete', + _remove_theme, + ButtonAction.DESTRUCTIVE), + } + + NewConfirmationDialog('Delete Theme', + 'Do you want to permanently delete this theme?', + buttons, + transient_for=self) + + @staticmethod + def _on_destroy(*args): + window = app.get_app_window('Preferences') + if window is not None: + window.update_theme_list() + + +class Option(Gtk.ListBoxRow): + def __init__(self, option, value): + Gtk.ListBoxRow.__init__(self) + self._option = option + self._box = Gtk.Box(spacing=12) + + label = Gtk.Label() + label.set_text(option.label) + label.set_hexpand(True) + label.set_halign(Gtk.Align.START) + self._box.add(label) + + if option.attr in (StyleAttr.COLOR, StyleAttr.BACKGROUND): + self._init_color(value) + elif option.attr == StyleAttr.FONT: + self._init_font(value) + + remove_button = Gtk.Button.new_from_icon_name( + 'list-remove-symbolic', Gtk.IconSize.MENU) + remove_button.get_style_context().add_class('theme_remove_button') + remove_button.connect('clicked', self._on_remove) + self._box.add(remove_button) + + self.add(self._box) + self.show_all() + + def _init_color(self, color): + color_button = Gtk.ColorButton() + if color is not None: + rgba = Gdk.RGBA() + rgba.parse(color) + color_button.set_rgba(rgba) + color_button.set_halign(Gtk.Align.END) + color_button.connect('color-set', self._on_color_set) + self._box.add(color_button) + + def _init_font(self, desc): + font_button = Gtk.FontButton() + if desc is not None: + font_button.set_font_desc(desc) + font_button.set_halign(Gtk.Align.END) + font_button.connect('font-set', self._on_font_set) + self._box.add(font_button) + + def _on_color_set(self, color_button): + color = color_button.get_rgba() + color_string = color.to_string() + app.css_config.set_value( + self._option.selector, self._option.attr, color_string, pre=True) + app.nec.push_incoming_event(StyleChanged(None)) + + def _on_font_set(self, font_button): + desc = font_button.get_font_desc() + app.css_config.set_font(self._option.selector, desc, pre=True) + app.nec.push_incoming_event(StyleChanged(None,)) + + def _on_remove(self, *args): + self.get_parent().remove(self) + app.css_config.remove_value( + self._option.selector, self._option.attr, pre=True) + app.nec.push_incoming_event(StyleChanged(None)) + self.destroy() + + def __eq__(self, other): + if isinstance(other, ChooseOption): + return other.option == self._option + else: + return other._option == self._option + + +class ChooseOption(Gtk.ListBoxRow): + def __init__(self, option): + Gtk.ListBoxRow.__init__(self) + self.option = option + label = Gtk.Label(option.label) + label.set_xalign(0) + self.add(label) + self.show_all() diff --git a/gajim/gtk/util.py b/gajim/gtk/util.py index 8eb9018dd..4c918f6b4 100644 --- a/gajim/gtk/util.py +++ b/gajim/gtk/util.py @@ -207,3 +207,14 @@ def python_month(month): def gtk_month(month): return month - 1 + + +def convert_rgb_to_hex(rgb_string): + rgb = Gdk.RGBA() + rgb.parse(rgb_string) + rgb.to_color() + + red = int(rgb.red * 255) + green = int(rgb.green * 255) + blue = int(rgb.blue * 255) + return '#%02x%02x%02x' % (red, green, blue) diff --git a/gajim/gtk/xml_console.py b/gajim/gtk/xml_console.py index 6f1700e01..ccbd8a8a6 100644 --- a/gajim/gtk/xml_console.py +++ b/gajim/gtk/xml_console.py @@ -21,7 +21,7 @@ from gi.repository import GLib from gajim.common import app from gajim.common import ged -from gajim.common.const import Option, OptionKind, OptionType +from gajim.common.const import Option, OptionKind, OptionType, StyleAttr from gajim.gtk import ErrorDialog from gajim.gtk import util from gajim.gtk.util import get_builder @@ -97,8 +97,10 @@ class XMLConsoleWindow(Gtk.Window): def create_tags(self): buffer_ = self.textview.get_buffer() - in_color = app.config.get('inmsgcolor') - out_color = app.config.get('outmsgcolor') + in_color = app.css_config.get_value( + '.gajim-incoming-nickname', StyleAttr.COLOR) + out_color = app.css_config.get_value( + '.gajim-outgoing-nickname', StyleAttr.COLOR) tags = ['presence', 'message', 'stream', 'iq'] diff --git a/gajim/gtkgui_helpers.py b/gajim/gtkgui_helpers.py index a80dad572..b170f0fe8 100644 --- a/gajim/gtkgui_helpers.py +++ b/gajim/gtkgui_helpers.py @@ -38,6 +38,7 @@ import os import sys import math import xml.etree.ElementTree as ET + try: from PIL import Image except: @@ -172,22 +173,6 @@ def get_completion_liststore(entry): entry.set_completion(completion) return liststore -def get_theme_font_for_option(theme, option): - """ - Return string description of the font, stored in theme preferences - """ - font_name = app.config.get_per('themes', theme, option) - font_desc = Pango.FontDescription() - font_prop_str = app.config.get_per('themes', theme, option + 'attrs') - if font_prop_str: - if font_prop_str.find('B') != -1: - font_desc.set_weight(Pango.Weight.BOLD) - if font_prop_str.find('I') != -1: - font_desc.set_style(Pango.Style.ITALIC) - fd = Pango.FontDescription(font_name) - fd.merge(font_desc, True) - return fd.to_string() - def move_window(window, x, y): """ Move the window, but also check if out of screen @@ -812,71 +797,19 @@ def __label_size_allocate(widget, allocation): def get_action(action): return app.app.lookup_action(action) -def load_css(): - path = os.path.join(configpaths.get('DATA'), 'style', 'gajim.css') - try: - with open(path, "r") as f: - css = f.read() - except Exception as exc: - print('Error loading css: %s', exc) - return +def add_css_class(widget, class_name, prefix=None): + if class_name and prefix: + class_name = prefix + class_name - provider = Gtk.CssProvider() - css = "\n".join((css, convert_config_to_css())) - provider.load_from_data(bytes(css.encode())) - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - -def convert_config_to_css(): - css = '' - themed_widgets = { - 'ChatControl-BannerEventBox': ('bannerbgcolor', 'background'), - 'ChatControl-BannerNameLabel': ('bannertextcolor', 'color'), - 'ChatControl-BannerLabel': ('bannertextcolor', 'color'), - 'GroupChatControl-BannerEventBox': ('bannerbgcolor', 'background'), - 'GroupChatControl-BannerNameLabel': ('bannertextcolor', 'color'), - 'GroupChatControl-BannerLabel': ('bannertextcolor', 'color'), - 'Discovery-BannerEventBox': ('bannerbgcolor', 'background'), - 'Discovery-BannerLabel': ('bannertextcolor', 'color')} - - classes = {'state_composing_color': ('', 'color'), - 'state_inactive_color': ('', 'color'), - 'state_gone_color': ('', 'color'), - 'state_paused_color': ('', 'color'), - 'msgcorrectingcolor': ('text', 'background'), - 'state_muc_directed_msg_color': ('', 'color'), - 'state_muc_msg_color': ('', 'color')} - - - theme = app.config.get('roster_theme') - for key, values in themed_widgets.items(): - config, attr = values - css += '#{} {{'.format(key) - value = app.config.get_per('themes', theme, config) - if value: - css += '{attr}: {color};\n'.format(attr=attr, color=value) - css += '}\n' - - for key, values in classes.items(): - node, attr = values - value = app.config.get_per('themes', theme, key) - if value: - css += '.theme_{cls} {node} {{ {attr}: {color}; }}\n'.format( - cls=key, node=node, attr=attr, color=value) - - css += add_css_font() - - return css - -def add_css_class(widget, class_name): style = widget.get_style_context() - for css_cls in style.list_classes(): - if css_cls.startswith('theme_'): - style.remove_class(css_cls) - if class_name: - style.add_class('theme_' + class_name) + if prefix is not None: + # Remove all css classes with prefix + for css_cls in style.list_classes(): + if css_cls.startswith(prefix): + style.remove_class(css_cls) + + if class_name is not None: + style.add_class(class_name) def add_css_to_widget(widget, css): provider = Gtk.CssProvider() @@ -887,28 +820,7 @@ def add_css_to_widget(widget, css): def remove_css_class(widget, class_name): style = widget.get_style_context() - style.remove_class('theme_' + class_name) - -def add_css_font(): - conversation_font = app.config.get('conversation_font') - if not conversation_font: - return '' - font = Pango.FontDescription(conversation_font) - unit = "pt" if Gtk.check_version(3, 22, 0) is None else "px" - css = """ - .font_custom {{ - font-family: "{family}"; - font-size: {size}{unit}; - font-weight: {weight}; - }}""".format( - family=font.get_family(), - size=int(round(font.get_size() / Pango.SCALE)), - unit=unit, - weight=pango_to_css_weight(font.get_weight())) - css = css.replace("font-size: 0{unit};".format(unit=unit), "") - css = css.replace("font-weight: 0;", "") - css = "\n".join(filter(lambda x: x.strip(), css.splitlines())) - return css + style.remove_class(class_name) def draw_affiliation(surface, affiliation): icon_size = 16 diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 4c6e16823..aa08ae09d 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -2655,15 +2655,6 @@ class Interface: self.gpg_passphrase = {} self.pass_dialog = {} self.db_error_dialog = None - self.default_colors = { - 'inmsgcolor': app.config.get('inmsgcolor'), - 'outmsgcolor': app.config.get('outmsgcolor'), - 'inmsgtxtcolor': app.config.get('inmsgtxtcolor'), - 'outmsgtxtcolor': app.config.get('outmsgtxtcolor'), - 'statusmsgcolor': app.config.get('statusmsgcolor'), - 'urlmsgcolor': app.config.get('urlmsgcolor'), - 'markedmsgcolor': app.config.get('markedmsgcolor'), - } self.handlers = {} self.roster = None @@ -2684,6 +2675,9 @@ class Interface: # enable plugin_installer by default when creating config file app.config.set_per('plugins', 'plugin_installer', 'active', True) + # Load CSS files + app.load_css_config() + app.logger.reset_shown_unread_messages() # override logging settings from config (don't take care of '-q' option) if app.config.get('verbose'): @@ -2721,24 +2715,7 @@ class Interface: default[msg][4]) app.config.set_per('statusmsg', msg, 'mood_text', default[msg][5]) - #add default themes if there is not in the config file - theme = app.config.get('roster_theme') - if not theme in app.config.get_per('themes'): - app.config.set('roster_theme', _('default')) - if len(app.config.get_per('themes')) == 0: - d = ['accounttextcolor', 'accountbgcolor', 'accountfont', - 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', - 'groupfont', 'groupfontattrs', 'contacttextcolor', - 'contactbgcolor', 'contactfont', 'contactfontattrs', - 'bannertextcolor', 'bannerbgcolor'] - default = app.config.themes_default - for theme_name in default: - app.config.add_per('themes', theme_name) - theme = default[theme_name] - for o in d: - app.config.set_per('themes', theme_name, o, - theme[d.index(o)]) # Add Tor proxy if there is not in the config if len(app.config.get_per('proxies')) == 0: default = app.config.proxies_default diff --git a/gajim/history_manager.py b/gajim/history_manager.py index befff38d6..a87157e6b 100644 --- a/gajim/history_manager.py +++ b/gajim/history_manager.py @@ -48,7 +48,7 @@ from gi.repository import Gio from gajim.common import i18n from gajim.common import configpaths - +from gajim.common.const import StyleAttr def is_standalone(): # Determine if we are in standalone mode @@ -395,14 +395,14 @@ class HistoryManager: if kind in (KindConstant.SINGLE_MSG_RECV, KindConstant.CHAT_MSG_RECV, KindConstant.GC_MSG): # it is the other side - color = app.config.get('inmsgcolor') # so incoming color + color = app.css_config.get_value('.gajim-incoming-nickname', StyleAttr.COLOR) # so incoming color elif kind in (KindConstant.SINGLE_MSG_SENT, KindConstant.CHAT_MSG_SENT): # it is us - color = app.config.get('outmsgcolor') # so outgoing color + color = app.css_config.get_value('.gajim-outgoing-nickname', StyleAttr.COLOR) # so outgoing color elif kind in (KindConstant.STATUS, KindConstant.GCSTATUS): # is is statuses # so status color - color = app.config.get('statusmsgcolor') + color = app.css_config.get_value('.gajim-status-message', StyleAttr.COLOR) # include status into (status) message if message is None: message = '' diff --git a/gajim/htmltextview.py b/gajim/htmltextview.py index 1958a01e0..75e5e9b82 100644 --- a/gajim/htmltextview.py +++ b/gajim/htmltextview.py @@ -57,6 +57,8 @@ from gajim.gtk.util import get_builder from gajim.common import helpers from gajim.gtk import JoinGroupchatWindow from gajim.gtk import AddNewContactWindow +from gajim.common.const import StyleAttr + import logging log = logging.getLogger('gajim.htmlview') @@ -514,7 +516,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler): tag.href = href tag.type_ = type_ # to be used by the URL handler tag.connect('event', self.textview.hyperlink_handler, 'url') - tag.set_property('foreground', app.config.get('urlmsgcolor')) + tag.set_property('foreground', app.css_config.get_value('.gajim-url', StyleAttr.COLOR)) tag.set_property('underline', Pango.Underline.SINGLE) tag.is_anchor = True if title: @@ -855,7 +857,7 @@ class HtmlTextView(Gtk.TextView): buffer_ = self.get_buffer() self.tagURL = buffer_.create_tag('url') - color = app.config.get('urlmsgcolor') + color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) self.tagURL.set_property('foreground', color) self.tagURL.set_property('underline', Pango.Underline.SINGLE) self.tagURL.connect('event', self.hyperlink_handler, 'url') diff --git a/gajim/message_textview.py b/gajim/message_textview.py index ad090bf28..0b099e9c5 100644 --- a/gajim/message_textview.py +++ b/gajim/message_textview.py @@ -55,7 +55,7 @@ class MessageTextView(Gtk.TextView): self.set_right_margin(2) self.set_pixels_above_lines(2) self.set_pixels_below_lines(2) - self.get_style_context().add_class('font_custom') + self.get_style_context().add_class('gajim-conversation-font') # set undo list self.undo_list = [] diff --git a/gajim/message_window.py b/gajim/message_window.py index cc417ce31..1e9818d72 100644 --- a/gajim/message_window.py +++ b/gajim/message_window.py @@ -643,15 +643,14 @@ class MessageWindow(object): if isinstance(ctrl, ChatControl): tab_label_str = ctrl.get_tab_label() # Set Label Color - class_name = 'state_{}_color'.format(chatstate) - gtkgui_helpers.add_css_class(nick_label, class_name) + gtkgui_helpers.add_css_class(nick_label, chatstate, 'gajim-state-') else: tab_label_str, color = ctrl.get_tab_label(chatstate) # Set Label Color if color == 'active': - gtkgui_helpers.add_css_class(nick_label, None) + gtkgui_helpers.add_css_class(nick_label, None, 'gajim-state-') elif color is not None: - gtkgui_helpers.add_css_class(nick_label, color) + gtkgui_helpers.add_css_class(nick_label, color, 'gajim-state-') nick_label.set_markup(tab_label_str) diff --git a/gajim/roster_window.py b/gajim/roster_window.py index f1ef36885..769b06bfb 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -57,6 +57,7 @@ from gajim import tooltips from gajim import message_control from gajim import adhoc_commands from gajim.accounts_window import AccountsWindow + from gajim.gtk import JoinGroupchatWindow from gajim.gtk import ConfirmationDialogCheck from gajim.gtk import ConfirmationDialog @@ -73,14 +74,12 @@ from gajim.gtk import AccountCreationWizard from gajim.gtk import ServiceRegistration from gajim.gtk import HistoryWindow -from gajim.common.const import AvatarSize - from gajim.common import app from gajim.common import helpers from gajim.common import idle from gajim.common.exceptions import GajimGeneralException from gajim.common import i18n -from gajim.common.const import PEPEventType +from gajim.common.const import PEPEventType, AvatarSize, StyleAttr if app.is_installed('GEOCLUE'): from gajim.common import location_listener from gajim.common import ged @@ -1265,15 +1264,11 @@ class RosterWindow: status = helpers.reduce_chars_newlines(status, max_lines = 1) # escape markup entities and make them small - # italic and fg color color is calculated to be - # always readable - color = gtkgui_helpers.get_fade_color(self.tree, selected, - focus) - colorstring = '#%04x%04x%04x' % (int(color.red * 65535), - int(color.green * 65535), int(color.blue * 65535)) - name += '\n%s' % (colorstring, - GLib.markup_escape_text(status)) + + # italic + name += ('\n{}'.format( + GLib.markup_escape_text(status))) icon_name = helpers.get_icon_name_to_show(contact, account) # look if another resource has awaiting events @@ -4688,6 +4683,9 @@ class RosterWindow: gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) + def _style_changed(self, *args): + self.change_roster_style(None) + def _change_style(self, model, path, titer, option): if option is None or model[titer][Column.TYPE] == option: # We changed style for this type of row @@ -4751,18 +4749,18 @@ class RosterWindow: return theme = app.config.get('roster_theme') if type_ == 'account': - color = app.config.get_per('themes', theme, 'accounttextcolor') - renderer.set_property('foreground', color or None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) + color = app.css_config.get_value('.gajim-account-row', StyleAttr.COLOR) + renderer.set_property('foreground', color) + desc = app.css_config.get_font('.gajim-account-row') + renderer.set_property('font-desc', desc) renderer.set_property('xpad', 0) renderer.set_property('width', 3) self._set_account_row_background_color(renderer) elif type_ == 'group': - color = app.config.get_per('themes', theme, 'grouptextcolor') - renderer.set_property('foreground', color or None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + color = app.css_config.get_value('.gajim-group-row', StyleAttr.COLOR) + renderer.set_property('foreground', color) + desc = app.css_config.get_font('.gajim-group-row') + renderer.set_property('font-desc', desc) parent_iter = model.iter_parent(titer) if model[parent_iter][Column.TYPE] == 'group': renderer.set_property('xpad', 8) @@ -4781,19 +4779,16 @@ class RosterWindow: ctrl = app.interface.minimized_controls[account].get(jid, None) if ctrl and ctrl.attention_flag: - color = app.config.get_per('themes', theme, - 'state_muc_directed_msg_color') - renderer.set_property('foreground', 'red') + color = app.css_config.get_value( + '.state_muc_directed_msg_color', StyleAttr.COLOR) + renderer.set_property('foreground', color) if not color: - color = app.config.get_per('themes', theme, - 'contacttextcolor') - if color: + color = app.css_config.get_value('.gajim-contact-row', StyleAttr.COLOR) renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) + self._set_contact_row_background_color(renderer, jid, account) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + desc = app.css_config.get_font('.gajim-contact-row') + renderer.set_property('font-desc', desc) parent_iter = model.iter_parent(titer) if model[parent_iter][Column.TYPE] == 'contact': renderer.set_property('xpad', 16) @@ -4886,25 +4881,25 @@ class RosterWindow: def _set_account_row_background_color(self, renderer): theme = app.config.get('roster_theme') - color = app.config.get_per('themes', theme, 'accountbgcolor') - renderer.set_property('cell-background', color or None) + color = app.css_config.get_value('.gajim-account-row', StyleAttr.BACKGROUND) + renderer.set_property('cell-background', color) def _set_contact_row_background_color(self, renderer, jid, account): theme = app.config.get('roster_theme') if jid in app.newly_added[account]: - renderer.set_property('cell-background', app.config.get( - 'just_connected_bg_color')) + renderer.set_property('cell-background', app.css_config.get_value( + '.gajim-roster-connected', StyleAttr.BACKGROUND)) elif jid in app.to_be_removed[account]: - renderer.set_property('cell-background', app.config.get( - 'just_disconnected_bg_color')) + renderer.set_property('cell-background', app.css_config.get_value( + '.gajim-roster-disconnected', StyleAttr.BACKGROUND)) else: - color = app.config.get_per('themes', theme, 'contactbgcolor') - renderer.set_property('cell-background', color or None) + color = app.css_config.get_value('.gajim-contact-row', StyleAttr.BACKGROUND) + renderer.set_property('cell-background', color) def _set_group_row_background_color(self, renderer): theme = app.config.get('roster_theme') - color = app.config.get_per('themes', theme, 'groupbgcolor') - renderer.set_property('cell-background', color or None) + color = app.css_config.get_value('.gajim-group-row', 'background') + renderer.set_property('cell-background', color) ################################################################################ ### Everything about building menus @@ -5945,3 +5940,5 @@ class RosterWindow: self._nec_decrypted_message_received) app.ged.register_event_handler('blocking', ged.GUI1, self._nec_blocking) + app.ged.register_event_handler('style-changed', ged.GUI1, + self._style_changed)