Update Features Dialog Design

This commit is contained in:
Daniel Brötzmann 2018-10-27 14:54:57 +02:00 committed by Philipp Hörist
parent 2a62209e1e
commit 5fb6032420
3 changed files with 266 additions and 135 deletions

View File

@ -1,17 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 --> <!-- Generated with glade 3.22.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.12"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="features_box"> <object class="GtkBox" id="features_box">
<property name="width_request">300</property> <property name="width_request">400</property>
<property name="height_request">530</property> <property name="height_request">500</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="margin_left">18</property> <property name="border_width">18</property>
<property name="margin_right">18</property>
<property name="margin_top">18</property>
<property name="margin_bottom">18</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
@ -19,9 +16,12 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">&lt;b&gt;List of possible features in Gajim:&lt;/b&gt;</property> <property name="label" translatable="yes">Listing of available features</property>
<property name="use_markup">True</property> <property name="use_markup">True</property>
<property name="xalign">0</property> <style>
<class name="bold"/>
<class name="dim-label"/>
</style>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -41,9 +41,10 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="enable_grid_lines">horizontal</property>
<signal name="cursor-changed" handler="on_features_treeview_cursor_changed" swapped="no"/> <signal name="cursor-changed" handler="on_features_treeview_cursor_changed" swapped="no"/>
<child internal-child="selection"> <child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection1"/> <object class="GtkTreeSelection"/>
</child> </child>
</object> </object>
</child> </child>
@ -55,29 +56,44 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkFrame"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="border_width">3</property> <property name="orientation">vertical</property>
<property name="label_xalign">0</property> <property name="spacing">6</property>
<property name="shadow_type">none</property> <child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Description</property>
<property name="use_markup">True</property>
<style>
<class name="bold"/>
<class name="dim-label"/>
<class name="margin-top6"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkLabel" id="feature_desc_label"> <object class="GtkLabel" id="feature_desc_label">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_left">12</property> <property name="halign">start</property>
<property name="wrap">True</property> <property name="wrap">True</property>
<property name="selectable">True</property> <property name="selectable">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
</object> </object>
</child> <packing>
<child type="label"> <property name="expand">False</property>
<object class="GtkLabel"> <property name="fill">True</property>
<property name="visible">True</property> <property name="position">1</property>
<property name="can_focus">False</property> </packing>
<property name="label" translatable="yes">&lt;b&gt;Description&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child> </child>
</object> </object>
<packing> <packing>
@ -86,5 +102,52 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Requirements</property>
<property name="use_markup">True</property>
<style>
<class name="bold"/>
<class name="dim-label"/>
<class name="margin-top6"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="feature_req_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object> </object>
</interface> </interface>

View File

@ -88,6 +88,13 @@ popover#EmoticonPopover { padding: 5px; background-color: @theme_unfocused_base_
#ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; } #ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; }
#ServerInfoGrid > list > row.activatable:active { box-shadow: none; } #ServerInfoGrid > list > row.activatable:active { box-shadow: none; }
/* Features Info */
#FeaturesInfoGrid > list { border: 1px solid; border-color: @borders; }
#FeaturesInfoGrid > list > row:first-child { border-top: 1px solid; border-color: @borders; }
#FeaturesInfoGrid > list > row { padding: 10px 20px 10px 10px; }
#FeaturesInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; }
#FeaturesInfoGrid > list > row.activatable:active { box-shadow: none; }
/* OptionsBox */ /* OptionsBox */
#OptionsBox > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } #OptionsBox > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; }
#OptionsBox > row:last-child { border-bottom: 0px} #OptionsBox > row:last-child { border-bottom: 0px}

View File

@ -20,142 +20,148 @@
# along with Gajim. If not, see <http://www.gnu.org/licenses/>. # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import os import os
from collections import namedtuple
import gi import gi
from gi.repository import Gtk, Gdk from gi.repository import Gtk
from gajim.common import app from gajim.common import app
from gajim.common.i18n import Q_
from gajim.common.i18n import _ from gajim.common.i18n import _
from gajim.gtk.util import get_builder
class FeaturesDialog(Gtk.Dialog): class FeaturesDialog(Gtk.Dialog):
def __init__(self): def __init__(self):
flags = Gtk.DialogFlags.DESTROY_WITH_PARENT flags = Gtk.DialogFlags.DESTROY_WITH_PARENT
super().__init__(_('Features'), None, flags) super().__init__(_('Features'), None, flags)
self.connect('key-press-event', self.on_key_press_event)
self.set_transient_for(app.interface.roster.window) self.set_transient_for(app.interface.roster.window)
self.set_resizable(False)
self.builder = get_builder('features_window.ui') grid = Gtk.Grid()
content = self.get_content_area() grid.set_name('FeaturesInfoGrid')
content.add(self.builder.get_object('features_box')) grid.set_row_spacing(10)
grid.set_hexpand(True)
treeview = self.builder.get_object('features_treeview') self.feature_listbox = Gtk.ListBox()
self.desc_label = self.builder.get_object('feature_desc_label') self.feature_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
self.feature_listbox.set_header_func(self.header_func, _('Features'))
# {name: (available_function, unix_text, windows_text)} grid.attach(self.feature_listbox, 0, 0, 1, 1)
self.features = {
_('Bonjour / Zeroconf'): (
self.zeroconf_available,
_('Serverless chatting with autodetected clients in a local network.'),
_('Requires python-dbus.'),
_('Requires pybonjour and bonjour SDK running (%(url)s)') % {'url': 'https://developer.apple.com/opensource/).'}),
_('Command line'): (
self.dbus_available,
_('A script to control Gajim via commandline.'),
_('Requires python-dbus.'),
_('Feature not available under Windows.')),
_('OpenPGP message encryption'): (
self.gpg_available,
_('Ability to encrypting chat messages with OpenPGP.'),
_('Requires gpg and python-gnupg (%(url)s).') % {'url': 'https://bitbucket.org/vinay.sajip/python-gnupg'},
_('Requires gpg.exe in PATH.')),
_('Password encryption'): (
self.some_keyring_available,
_('Passwords can be stored securely and not just in plaintext.'),
_('Requires libsecret and a provider (such as GNOME Keyring and KSecretService).'),
_('On Windows the Windows Credential Vault is used.')),
_('Spell Checker'): (
self.speller_available,
_('Spellchecking of composed messages.'),
_('Requires Gspell'),
_('Requires Gspell')),
_('Automatic status'): (
self.idle_available,
_('Ability to measure idle time, in order to set auto status.'),
_('Requires libxss library.'),
_('Requires python2.5.')),
_('RST Generator'): (
self.docutils_available,
_('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
_('Requires python-docutils.'),
_('Requires python-docutils.')),
_('Audio / Video'): (
self.farstream_available,
_('Ability to start audio and video chat.'),
_('Requires gir1.2-farstream-0.2, gir1.2-gstreamer-1.0, gstreamer1.0-libav and gstreamer1.0-plugins-ugly.'),
_('Feature not available under Windows.')),
_('UPnP-IGD'): (
self.gupnp_igd_available,
_('Ability to request your router to forward port for file transfer.'),
_('Requires gir1.2-gupnpigd-1.0.'),
_('Feature not available under Windows.')),
}
# name, supported box = self.get_content_area()
self.model = Gtk.ListStore(str, bool) box.pack_start(grid, True, True, 0)
treeview.set_model(self.model) box.set_property('margin', 12)
box.set_spacing(18)
col = Gtk.TreeViewColumn(Q_('?features:Available')) self.connect('response', self.on_response)
treeview.append_column(col)
cell = Gtk.CellRendererToggle()
cell.set_property('radio', True)
col.pack_start(cell, True)
col.add_attribute(cell, 'active', 1)
col = Gtk.TreeViewColumn(_('Feature')) for feature in self.get_features():
treeview.append_column(col) self.add_feature(feature)
cell = Gtk.CellRendererText()
col.pack_start(cell, True)
col.add_attribute(cell, 'text', 0)
# Fill model
for feature in self.features:
func = self.features[feature][0]
rep = func()
self.model.append([feature, rep])
self.model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
self.builder.connect_signals(self)
self.show_all() self.show_all()
def on_key_press_event(self, widget, event): @staticmethod
if event.keyval == Gdk.KEY_Escape: def header_func(row, before, user_data):
if before:
row.set_header(None)
else:
label = Gtk.Label(label=user_data)
label.set_halign(Gtk.Align.START)
row.set_header(label)
def on_response(self, dialog, response):
if response == Gtk.ResponseType.OK:
self.destroy() self.destroy()
def on_close_button_clicked(self, widget): def add_feature(self, feature):
self.destroy() item = FeatureItem(feature)
self.feature_listbox.add(item)
item.get_parent().set_tooltip_text(item.tooltip)
def on_features_treeview_cursor_changed(self, widget): def get_features(self):
selection = widget.get_selection() Feature = namedtuple('Feature',
if not selection: ['name', 'available', 'tooltip',
return 'dependency_u', 'dependency_w', 'enabled'])
rows = selection.get_selected_rows()[1]
if not rows:
return
path = rows[0]
feature = self.model[path][0]
text = self.features[feature][1] + '\n'
if os.name == 'nt':
text = text + self.features[feature][3]
else:
text = text + self.features[feature][2]
self.desc_label.set_text(text)
def zeroconf_available(self): spell_check_enabled = app.config.get('use_speller')
return app.is_installed('ZEROCONF')
auto_status = [app.config.get('autoaway'), app.config.get('autoxa')]
auto_status_enabled = bool(any(auto_status))
return [
Feature(_('Audio / Video'),
app.is_installed('FARSTREAM'),
_('Enables Gajim to provide Audio and Video chats'),
_('Requires: gir1.2-farstream-0.2, gir1.2-gstreamer-1.0, '
'gstreamer1.0-libav, gstreamer1.0-plugins-ugly'),
_('Feature not available under Windows'),
None),
Feature(_('Automatic Status'),
self.idle_available(),
_('Enables Gajim to measure your computer\'s idle time in '
'order to set your Status automatically'),
_('Requires: libxss'),
_('No additional requirements'),
auto_status_enabled),
Feature(_('Bonjour / Zeroconf (Serverless Chat)'),
app.is_installed('ZEROCONF'),
_('Enables Gajim to automatically detected clients in a '
'local network for serverless chats'),
_('Requires: python-dbus'),
_('Requires: pybonjour and bonjour SDK running (%(url)s)')
% {'url': 'https://developer.apple.com/opensource/)'},
None),
Feature(_('Command line Control'),
self.dbus_available(),
_('Enables you to control Gajim with via commandline'),
_('Requires: python-dbus'),
_('Feature not available under Windows'),
None),
Feature(_('OpenPGP Message Encryption'),
app.is_installed('GPG'),
_('Enables Gajim to encrypt chat messages with OpenPGP'),
_('Requires: gpg and python-gnupg (%(url)s)')
% {'url': 'https://bitbucket.org/vinay.sajip/python-gnupg'},
_('Requires: gpg.exe in your PATH environment variable'),
None),
Feature(_('RST XHTML Generator'),
self.docutils_available(),
_('Enables Gajim to generate XHTML output from RST '
'code (%(url)s)') % {'url':
'http://docutils.sourceforge.net/docs/ref/rst/'
'restructuredtext.html'},
_('Requires: python-docutils'),
_('Requires: python-docutils'),
None),
Feature(_('Secure Password Storage'),
self.some_keyring_available(),
_('Enables Gajim to store Passwords securely instead of '
'storing them in plaintext'),
_('Requires: libsecret and a provider (such as GNOME '
'Keyring and KSecretService)'),
_('Windows Credential Vault is used for secure password '
'storage'),
None),
Feature(_('Spell Checker'),
app.is_installed('GSPELL'),
_('Enables Gajim to spell check your messages while '
'composing'),
_('Requires: Gspell'),
_('Requires: Gspell'),
spell_check_enabled),
Feature(_('UPnP-IGD Port Forwarding'),
app.is_installed('UPNP'),
_('Enables Gajim to request your router to forward ports '
'for file transfers'),
_('Requires: gir1.2-gupnpigd-1.0'),
_('Feature not available under Windows'),
None)
]
def dbus_available(self): def dbus_available(self):
from gajim.common import dbus_support from gajim.common import dbus_support
return dbus_support.supported return dbus_support.supported
def gpg_available(self):
return app.is_installed('GPG')
def some_keyring_available(self): def some_keyring_available(self):
if os.name == 'nt': if os.name == 'nt':
return True return True
@ -166,9 +172,6 @@ class FeaturesDialog(Gtk.Dialog):
return False return False
return True return True
def speller_available(self):
return app.is_installed('GSPELL')
def idle_available(self): def idle_available(self):
from gajim.common import idle from gajim.common import idle
return idle.Monitor.is_available() return idle.Monitor.is_available()
@ -180,8 +183,66 @@ class FeaturesDialog(Gtk.Dialog):
return False return False
return True return True
def farstream_available(self):
return app.is_installed('FARSTREAM')
def gupnp_igd_available(self): class FeatureItem(Gtk.Grid):
return app.is_installed('UPNP') def __init__(self, feature):
super().__init__()
self.set_column_spacing(12)
self.tooltip = feature.tooltip
self.feature_dependency_u_text = feature.dependency_u
self.feature_dependency_w_text = feature.dependency_w
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.feature_label = Gtk.Label(label=feature.name)
self.feature_label.set_halign(Gtk.Align.START)
self.box.pack_start(self.feature_label, True, True, 0)
self.feature_dependency_u = Gtk.Label(label=feature.dependency_u)
self.feature_dependency_u.get_style_context().add_class('dim-label')
self.feature_dependency_w = Gtk.Label(label=feature.dependency_w)
self.feature_dependency_w.get_style_context().add_class('dim-label')
if not feature.available:
self.feature_dependency_u.set_halign(Gtk.Align.START)
self.feature_dependency_u.set_alignment(0.0, 0.0)
self.feature_dependency_u.set_line_wrap(True)
self.feature_dependency_u.set_max_width_chars(50)
self.feature_dependency_u.set_selectable(True)
self.feature_dependency_w.set_halign(Gtk.Align.START)
self.feature_dependency_w.set_alignment(0.0, 0.0)
self.feature_dependency_w.set_line_wrap(True)
self.feature_dependency_w.set_max_width_chars(50)
self.feature_dependency_w.set_selectable(True)
if os.name == 'nt':
self.box.pack_start(self.feature_dependency_w, True, True, 0)
else:
self.box.pack_start(self.feature_dependency_u, True, True, 0)
self.icon = Gtk.Image()
self.label_disabled = Gtk.Label(label='Disabled in Preferences')
self.label_disabled.get_style_context().add_class('dim-label')
self.set_feature(feature.available, feature.enabled)
self.add(self.icon)
self.add(self.box)
def set_feature(self, available, enabled):
self.icon.get_style_context().remove_class('error-color')
self.icon.get_style_context().remove_class('warning-color')
self.icon.get_style_context().remove_class('success-color')
if not available:
self.icon.set_from_icon_name('window-close-symbolic',
Gtk.IconSize.MENU)
self.icon.get_style_context().add_class('error-color')
elif enabled is False:
self.icon.set_from_icon_name('dialog-warning-symbolic',
Gtk.IconSize.MENU)
self.box.pack_start(self.label_disabled, True, True, 0)
self.icon.get_style_context().add_class('warning-color')
else:
self.icon.set_from_icon_name('emblem-ok-symbolic',
Gtk.IconSize.MENU)
self.icon.get_style_context().add_class('success-color')