Commit 8e8bba71 authored by Fabián Orccón's avatar Fabián Orccón Committed by Alexandru Băluț

plugins: Add preferences for text font and color in the Developer Console

parent 5b6ea8a3
Pipeline #24548 passed with stages
in 66 minutes and 53 seconds
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
import configparser import configparser
import os import os
from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GObject from gi.repository import GObject
...@@ -137,6 +138,23 @@ class GlobalSettings(GObject.Object, Loggable): ...@@ -137,6 +138,23 @@ class GlobalSettings(GObject.Object, Loggable):
self._readSettingsFromConfigurationFile() self._readSettingsFromConfigurationFile()
self._readSettingsFromEnvironmentVariables() self._readSettingsFromEnvironmentVariables()
def reload_attribute_from_file(self, section, attrname):
"""Reads and sets an attribute from the configuration file.
Pitivi's default behavior is to set attributes from the configuration
file when starting and to save those attributes back to the file when
exiting the application. You can use this method when you need to
read an attribute during runtime (in the middle of the process).
"""
if section in self.options:
if attrname in self.options[section]:
type_, key, _ = self.options[section][attrname]
try:
value = self._read_value(section, key, type_)
except configparser.NoSectionError:
return
setattr(self, attrname, value)
def _read_value(self, section, key, type_): def _read_value(self, section, key, type_):
if type_ == int: if type_ == int:
try: try:
...@@ -152,6 +170,8 @@ class GlobalSettings(GObject.Object, Loggable): ...@@ -152,6 +170,8 @@ class GlobalSettings(GObject.Object, Loggable):
elif type_ == list: elif type_ == list:
tmp_value = self._config.get(section, key) tmp_value = self._config.get(section, key)
value = [token.strip() for token in tmp_value.split("\n") if token] value = [token.strip() for token in tmp_value.split("\n") if token]
elif type_ == Gdk.RGBA:
value = self.get_rgba(section, key)
else: else:
value = self._config.get(section, key) value = self._config.get(section, key)
return value return value
...@@ -160,6 +180,8 @@ class GlobalSettings(GObject.Object, Loggable): ...@@ -160,6 +180,8 @@ class GlobalSettings(GObject.Object, Loggable):
if type(value) == list: if type(value) == list:
value = "\n" + "\n".join(value) value = "\n" + "\n".join(value)
self._config.set(section, key, value) self._config.set(section, key, value)
elif type(value) == Gdk.RGBA:
self.set_rgba(section, key, value)
else: else:
self._config.set(section, key, str(value)) self._config.set(section, key, str(value))
...@@ -254,6 +276,33 @@ class GlobalSettings(GObject.Object, Loggable): ...@@ -254,6 +276,33 @@ class GlobalSettings(GObject.Object, Loggable):
"""Resets the specified setting to its default value.""" """Resets the specified setting to its default value."""
setattr(self, attrname, self.defaults[attrname]) setattr(self, attrname, self.defaults[attrname])
def get_rgba(self, section, option):
"""Gets the option value from the configuration file parsed as a RGBA.
Args:
section (str): The section.
option (str): The option that belongs to the `section`.
Returns:
Gdk.RGBA: The value for the `option` at the given `section`.
"""
value = self._config.get(section, option)
color = Gdk.RGBA()
if not color.parse(value):
raise Exception("Value cannot be parsed as Gdk.RGBA: %s" % value)
return color
def set_rgba(self, section, option, value):
"""Sets the option value to the configuration file as a RGBA.
Args:
section (str): The section.
option (str): The option that belongs to the `section`.
value (Gdk.RGBA): The color.
"""
value = value.to_string()
self._config.set(section, option, value)
@classmethod @classmethod
def addConfigOption(cls, attrname, type_=None, section=None, key=None, def addConfigOption(cls, attrname, type_=None, section=None, key=None,
environment=None, default=None, notify=False,): environment=None, default=None, notify=False,):
......
...@@ -20,12 +20,17 @@ ...@@ -20,12 +20,17 @@
import sys import sys
from gettext import gettext as _ from gettext import gettext as _
from gi.repository import Gdk
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Pango
from gi.repository import Peas from gi.repository import Peas
from utils import Namespace from utils import Namespace
from widgets import ConsoleWidget from widgets import ConsoleWidget
from pitivi.dialogs.prefs import PreferencesDialog
from pitivi.settings import ConfigError
class PitiviNamespace(Namespace): class PitiviNamespace(Namespace):
"""Easy to shape Python namespace.""" """Easy to shape Python namespace."""
...@@ -65,6 +70,11 @@ class Console(GObject.GObject, Peas.Activatable): ...@@ -65,6 +70,11 @@ class Console(GObject.GObject, Peas.Activatable):
__gtype_name__ = "ConsolePlugin" __gtype_name__ = "ConsolePlugin"
object = GObject.Property(type=GObject.Object) object = GObject.Property(type=GObject.Object)
DEFAULT_COLOR = Gdk.RGBA(1.0, 1.0, 1.0, 1.0)
DEFAULT_STDERR_COLOR = Gdk.RGBA(0.96, 0.47, 0.0, 1.0)
DEFAULT_STDOUT_COLOR = Gdk.RGBA(1.0, 1.0, 1.0, 1.0)
DEFAULT_FONT = Pango.FontDescription.from_string("Monospace Regular 12")
def __init__(self): def __init__(self):
GObject.GObject.__init__(self) GObject.GObject.__init__(self)
self.window = None self.window = None
...@@ -79,11 +89,80 @@ class Console(GObject.GObject, Peas.Activatable): ...@@ -79,11 +89,80 @@ class Console(GObject.GObject, Peas.Activatable):
def do_activate(self): def do_activate(self):
api = self.object api = self.object
self.app = api.app self.app = api.app
try:
self.app.settings.addConfigSection("console")
except ConfigError:
pass
try:
self.app.settings.addConfigOption(attrname="consoleColor",
section="console",
key="console-color",
notify=True,
default=Console.DEFAULT_COLOR)
except ConfigError:
pass
try:
self.app.settings.addConfigOption(attrname="consoleErrorColor",
section="console",
key="console-error-color",
notify=True,
default=Console.DEFAULT_STDERR_COLOR)
except ConfigError:
pass
try:
self.app.settings.addConfigOption(attrname="consoleOutputColor",
section="console",
key="console-output-color",
notify=True,
default=Console.DEFAULT_STDOUT_COLOR)
except ConfigError:
pass
try:
self.app.settings.addConfigOption(attrname="consoleFont",
section="console",
key="console-font",
notify=True,
default=Console.DEFAULT_FONT.to_string())
except ConfigError:
pass
self.app.settings.reload_attribute_from_file("console", "consoleColor")
self.app.settings.reload_attribute_from_file("console",
"consoleErrorColor")
self.app.settings.reload_attribute_from_file("console",
"consoleOutputColor")
self.app.settings.reload_attribute_from_file("console", "consoleFont")
PreferencesDialog.add_section("console", _("Console"))
PreferencesDialog.addColorPreference(attrname="consoleColor",
label=_("Color"),
description=None,
section="console")
PreferencesDialog.addColorPreference(attrname="consoleErrorColor",
# Translators: The color of the content from stderr.
label=_("Standard error color"),
description=None,
section="console")
PreferencesDialog.addColorPreference(attrname="consoleOutputColor",
# Translators: The color of the content from stdout.
label=_("Standard output color"),
description=None,
section="console")
PreferencesDialog.addFontPreference(attrname="consoleFont",
label=_("Font"),
description=None,
section="console")
self._setup_dialog() self._setup_dialog()
self.add_menu_item() self.add_menu_item()
self.menu_item.show() self.menu_item.show()
def do_deactivate(self): def do_deactivate(self):
PreferencesDialog.remove_section("console")
self.window.destroy() self.window.destroy()
self.remove_menu_item() self.remove_menu_item()
self.window = None self.window = None
...@@ -107,16 +186,20 @@ class Console(GObject.GObject, Peas.Activatable): ...@@ -107,16 +186,20 @@ class Console(GObject.GObject, Peas.Activatable):
def _setup_dialog(self): def _setup_dialog(self):
namespace = PitiviNamespace(self.app) namespace = PitiviNamespace(self.app)
self.window = Gtk.Window() self.window = Gtk.Window()
welcome_message = "".join(self.create_welcome_message(namespace)) welcome_message = "".join(self._create_welcome_message(namespace))
self.terminal = ConsoleWidget(namespace, welcome_message) self.terminal = ConsoleWidget(namespace, welcome_message)
self.terminal.connect("eof", self.__eof_cb) self.terminal.connect("eof", self.__eof_cb)
self._init_colors()
self.terminal.set_font(self.app.settings.consoleFont)
self._connect_settings_signals()
self.window.set_default_size(600, 400) self.window.set_default_size(600, 400)
self.window.set_title(_("Pitivi Console")) self.window.set_title(_("Pitivi Console"))
self.window.connect("delete-event", self.__delete_event_cb) self.window.connect("delete-event", self.__delete_event_cb)
self.window.add(self.terminal) self.window.add(self.terminal)
def create_welcome_message(self, namespace): def _create_welcome_message(self, namespace):
console_plugin_info = self.app.plugin_manager.get_plugin_info("console") console_plugin_info = self.app.plugin_manager.get_plugin_info("console")
name = console_plugin_info.get_name() name = console_plugin_info.get_name()
version = console_plugin_info.get_version() or "" version = console_plugin_info.get_version() or ""
...@@ -131,6 +214,37 @@ class Console(GObject.GObject, Peas.Activatable): ...@@ -131,6 +214,37 @@ class Console(GObject.GObject, Peas.Activatable):
yield _("Type \"{help}(<command>)\" for more information.").format(help="help") yield _("Type \"{help}(<command>)\" for more information.").format(help="help")
yield "\n\n" yield "\n\n"
def _init_colors(self):
"""Sets the colors from Pitivi settings."""
self.terminal.set_stderr_color(self.app.settings.consoleErrorColor)
self.terminal.set_stdout_color(self.app.settings.consoleOutputColor)
self.terminal.set_color(self.app.settings.consoleColor)
def _connect_settings_signals(self):
"""Connects the settings' signals."""
self.app.settings.connect("consoleColorChanged", self.__color_changed_cb)
self.app.settings.connect("consoleErrorColorChanged",
self.__error_color_changed_cb)
self.app.settings.connect("consoleOutputColorChanged",
self.__output_color_changed_cb)
self.app.settings.connect("consoleFontChanged", self.__font_changed_cb)
def __color_changed_cb(self, settings):
if self.terminal:
self.terminal.set_color(settings.consoleColor)
def __error_color_changed_cb(self, settings):
if self.terminal:
self.terminal.set_stderr_color(settings.consoleErrorColor)
def __output_color_changed_cb(self, settings):
if self.terminal:
self.terminal.set_stdout_color(settings.consoleOutputColor)
def __font_changed_cb(self, settings):
if self.terminal:
self.terminal.set_font(settings.consoleFont)
def __menu_item_activate_cb(self, unused_data): def __menu_item_activate_cb(self, unused_data):
self.window.show_all() self.window.show_all()
self.window.set_keep_above(True) self.window.set_keep_above(True)
......
...@@ -76,13 +76,16 @@ class ConsoleHistory(GObject.Object): ...@@ -76,13 +76,16 @@ class ConsoleHistory(GObject.Object):
class ConsoleBuffer(Gtk.TextBuffer): class ConsoleBuffer(Gtk.TextBuffer):
# pylint: disable=too-many-instance-attributes
def __init__(self, namespace, welcome_message=""): def __init__(self, namespace, welcome_message=""):
Gtk.TextBuffer.__init__(self) Gtk.TextBuffer.__init__(self)
self.prompt = sys.ps1 self.prompt = sys.ps1
self._stdout = FakeOut(self) self.output = self.create_tag("output")
self._stderr = FakeOut(self) self.error = self.create_tag("error")
self._stdout = FakeOut(self, self.output)
self._stderr = FakeOut(self, self.error)
self._console = code.InteractiveConsole(namespace) self._console = code.InteractiveConsole(namespace)
self.insert(self.get_end_iter(), welcome_message) self.insert(self.get_end_iter(), welcome_message)
...@@ -101,6 +104,9 @@ class ConsoleBuffer(Gtk.TextBuffer): ...@@ -101,6 +104,9 @@ class ConsoleBuffer(Gtk.TextBuffer):
cmd = self.get_command_line() cmd = self.get_command_line()
self.history.add(cmd) self.history.add(cmd)
before_prompt_iter = self.get_iter_at_mark(self.before_prompt_mark)
self.remove_all_tags(before_prompt_iter, self.get_end_iter())
with swap_std(self._stdout, self._stderr): with swap_std(self._stdout, self._stderr):
self.write("\n") self.write("\n")
is_command_incomplete = self._console.push(cmd) is_command_incomplete = self._console.push(cmd)
...@@ -109,6 +115,7 @@ class ConsoleBuffer(Gtk.TextBuffer): ...@@ -109,6 +115,7 @@ class ConsoleBuffer(Gtk.TextBuffer):
self.prompt = sys.ps2 self.prompt = sys.ps2
else: else:
self.prompt = sys.ps1 self.prompt = sys.ps1
self.move_mark(self.before_prompt_mark, self.get_end_iter())
self.write(self.prompt) self.write(self.prompt)
self.move_mark(self.prompt_mark, self.get_end_iter()) self.move_mark(self.prompt_mark, self.get_end_iter())
...@@ -121,9 +128,12 @@ class ConsoleBuffer(Gtk.TextBuffer): ...@@ -121,9 +128,12 @@ class ConsoleBuffer(Gtk.TextBuffer):
res = cursor_iter.compare(prompt_iter) res = cursor_iter.compare(prompt_iter)
return (before and res == -1) or (at and res == 0) or (after and res == 1) return (before and res == -1) or (at and res == 0) or (after and res == 1)
def write(self, text): def write(self, text, tag=None):
"""Writes a text to the buffer.""" """Writes a text to the buffer."""
self.insert(self.get_end_iter(), text) if tag is None:
self.insert(self.get_end_iter(), text)
else:
self.insert_with_tags(self.get_end_iter(), text, tag)
def get_command_line(self): def get_command_line(self):
"""Gets the last command line after the prompt. """Gets the last command line after the prompt.
...@@ -224,7 +234,7 @@ class ConsoleBuffer(Gtk.TextBuffer): ...@@ -224,7 +234,7 @@ class ConsoleBuffer(Gtk.TextBuffer):
self.place_cursor(end_iter) self.place_cursor(end_iter)
self.write(text) self.write(text)
def __insert_text_cb(self, buf, it, text, user_data): def __insert_text_cb(self, buf, unused_location, text, unused_len):
command = self.get_command_line() command = self.get_command_line()
if text == "\t" and command.strip() != "": if text == "\t" and command.strip() != "":
# If input text is '\t' and command doesn't start with spaces or tab # If input text is '\t' and command doesn't start with spaces or tab
......
...@@ -46,7 +46,7 @@ def display_autocompletion(last_obj, matches, text_buffer, ...@@ -46,7 +46,7 @@ def display_autocompletion(last_obj, matches, text_buffer,
if len(matches) == 1: if len(matches) == 1:
tokens = matches[0].split(last_obj) tokens = matches[0].split(last_obj)
if len(tokens) >= 1: if len(tokens) >= 1:
print(tokens[1], end="") text_buffer.insert(text_buffer.get_end_iter(), tokens[1])
elif len(matches) > 1: elif len(matches) > 1:
if new_command.startswith(old_command): if new_command.startswith(old_command):
# Complete the rest of the command if they have a common prefix. # Complete the rest of the command if they have a common prefix.
...@@ -106,12 +106,13 @@ class Namespace(dict): ...@@ -106,12 +106,13 @@ class Namespace(dict):
class FakeOut(TextIOBase): class FakeOut(TextIOBase):
"""Replacement for sys.stdout/err which redirects writes.""" """Replacement for sys.stdout/err which redirects writes."""
def __init__(self, buf): def __init__(self, buf, tag):
TextIOBase.__init__(self) TextIOBase.__init__(self)
self.buf = buf self.buf = buf
self.tag = tag
def write(self, string): def write(self, string):
self.buf.write(string) self.buf.write(string, self.tag)
def writelines(self, lines): def writelines(self, lines):
self.buf.write(lines) self.buf.write(lines, self.tag)
...@@ -16,15 +16,35 @@ ...@@ -16,15 +16,35 @@
# License along with this program; if not, write to the # License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
"""The developer console widget:""" """The developer console widget."""
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GObject from gi.repository import GObject
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Pango
from consolebuffer import ConsoleBuffer from consolebuffer import ConsoleBuffer
class ConsoleView(Gtk.TextView):
"""A TextView which removes tags from pasted text."""
def do_paste_clipboard(self):
# pylint: disable=arguments-differ
buf = self.get_buffer()
insert = buf.get_insert()
paste_mark = buf.create_mark("paste-mark", buf.get_iter_at_mark(insert),
left_gravity=True)
Gtk.TextView.do_paste_clipboard(self)
start = buf.get_iter_at_mark(paste_mark)
end = buf.get_iter_at_mark(insert)
buf.remove_all_tags(start, end)
buf.delete_mark(paste_mark)
class ConsoleWidget(Gtk.ScrolledWindow): class ConsoleWidget(Gtk.ScrolledWindow):
"""An emulated Python console. """An emulated Python console.
...@@ -40,7 +60,7 @@ class ConsoleWidget(Gtk.ScrolledWindow): ...@@ -40,7 +60,7 @@ class ConsoleWidget(Gtk.ScrolledWindow):
def __init__(self, namespace, welcome_message=""): def __init__(self, namespace, welcome_message=""):
Gtk.ScrolledWindow.__init__(self) Gtk.ScrolledWindow.__init__(self)
self._view = Gtk.TextView() self._view = ConsoleView()
buf = ConsoleBuffer(namespace, welcome_message) buf = ConsoleBuffer(namespace, welcome_message)
self._view.set_buffer(buf) self._view.set_buffer(buf)
self._view.set_editable(True) self._view.set_editable(True)
...@@ -50,6 +70,21 @@ class ConsoleWidget(Gtk.ScrolledWindow): ...@@ -50,6 +70,21 @@ class ConsoleWidget(Gtk.ScrolledWindow):
buf.connect("mark-set", self.__mark_set_cb) buf.connect("mark-set", self.__mark_set_cb)
buf.connect("insert-text", self.__insert_text_cb) buf.connect("insert-text", self.__insert_text_cb)
# Font color and style.
self._provider = Gtk.CssProvider()
self._css_values = {
"textview": {
"font-family": None,
"font-size": None,
"font-style": None,
"font-variant": None,
"font-weight": None
},
"textview > *": {
"color": None
}
}
def scroll_to_end(self): def scroll_to_end(self):
"""Scrolls the view to the end.""" """Scrolls the view to the end."""
end_iter = self._view.get_buffer().get_end_iter() end_iter = self._view.get_buffer().get_end_iter()
...@@ -57,6 +92,52 @@ class ConsoleWidget(Gtk.ScrolledWindow): ...@@ -57,6 +92,52 @@ class ConsoleWidget(Gtk.ScrolledWindow):
xalign=0, yalign=0) xalign=0, yalign=0)
return False return False
def set_font(self, font_desc):
"""Sets the font.
Args:
font (str): a PangoFontDescription as a string.
"""
pango_font_desc = Pango.FontDescription.from_string(font_desc)
self._css_values["textview"]["font-family"] = pango_font_desc.get_family()
self._css_values["textview"]["font-size"] = "%dpt" % int(pango_font_desc.get_size() / Pango.SCALE)
self._css_values["textview"]["font-style"] = pango_font_desc.get_style().value_nick
self._css_values["textview"]["font-variant"] = pango_font_desc.get_variant().value_nick
self._css_values["textview"]["font-weight"] = int(pango_font_desc.get_weight())
self._apply_css()
def set_color(self, color):
"""Sets the color.
Args:
color (Gdk.RGBA): a color.
"""
self._css_values["textview > *"]["color"] = color.to_string()
self._apply_css()
def _apply_css(self):
css = ""
for css_klass, props in self._css_values.items():
css += "%s {" % css_klass
for prop, value in props.items():
if value is not None:
css += "%s: %s;" % (prop, value)
css += "} "
css = css.encode("UTF-8")
self._provider.load_from_data(css)
Gtk.StyleContext.add_provider(self._view.get_style_context(),
self._provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def set_stdout_color(self, color):
"""Sets the color of the stdout text."""
self._view.get_buffer().output.set_property("foreground-rgba", color)
def set_stderr_color(self, color):
"""Sets the color of the stderr text."""
self._view.get_buffer().error.set_property("foreground-rgba", color)
# pylint: disable=too-many-return-statements
def __key_press_event_cb(self, view, event): def __key_press_event_cb(self, view, event):
buf = view.get_buffer() buf = view.get_buffer()
state = event.state & Gtk.accelerator_get_default_mod_mask() state = event.state & Gtk.accelerator_get_default_mod_mask()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment