# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# PyRoom - A clone of WriteRoom
# Copyright (c) 2007 Nicolas P. Rougier & NoWhereMan
# Copyright (c) 2008 The Pyroom Team - See AUTHORS file for more information
#
# This program 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.
#
# This program 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
# this program. If not, see .
# -----------------------------------------------------------------------------
"""
basic global GUI
Additionally allows user to apply custom settings
"""
import gtk
import gobject
import pango
import gtk.glade
import ConfigParser
import os
from sys import platform
if platform == 'win32':
data_home = os.environ['APPDATA']
else:
from xdg.BaseDirectory import xdg_data_home as data_home
from pyroom_error import PyroomError
class Theme(dict):
"""basically a dict with some utility methods"""
def __init__(self, theme_name):
theme_filename = self._lookup_theme(theme_name)
if not theme_filename:
raise PyroomError(_('theme not found: %s') % theme_name)
theme_file = ConfigParser.SafeConfigParser()
theme_file.read(theme_filename)
self.update(theme_file.items('theme'))
def _lookup_theme(self, theme_name):
"""lookup theme_filename for given theme_name
order of preference is homedir, global dir, source dir (if available)"""
local_directory = os.path.join(data_home, 'pyroom', 'themes')
global_directory = '/usr/share/pyroom/themes' # FIXME: platform
# in case PyRoom is run without installation
fallback_directory = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'..',
'themes'
)
for dirname in (local_directory, global_directory, fallback_directory):
filename = os.path.join(dirname, theme_name + '.theme')
if os.path.isfile(filename):
return filename
def save(self, filename):
"""save a theme"""
theme_file = ConfigParser.SafeConfigParser()
theme_file.add_section('theme')
for key, value in self.iteritems():
theme_file.set('theme', key, str(value))
theme_file.set('theme', 'name', os.path.basename(filename))
theme_file.write(open(filename + '.theme', 'w'))
class FadeLabel(gtk.Label):
""" GTK Label with timed fade out effect """
active_duration = 3000 # Fade start after this time
fade_duration = 1500.0 # Fade duration
def __init__(self, message='', active_color=None, inactive_color=None):
gtk.Label.__init__(self, message)
if not active_color:
active_color = '#ffffff'
self.active_color = active_color
if not inactive_color:
inactive_color = '#000000'
self.fade_level = 0
self.inactive_color = inactive_color
self.idle = 0
def set_text(self, message, duration=None):
"""change text that is displayed
@param message: message to display
@param duration: duration in miliseconds"""
if not duration:
duration = self.active_duration
self.modify_fg(gtk.STATE_NORMAL,
gtk.gdk.color_parse(self.active_color))
gtk.Label.set_text(self, message)
if self.idle:
gobject.source_remove(self.idle)
self.idle = gobject.timeout_add(duration, self.fade_start)
def fade_start(self):
"""start fading timer"""
self.fade_level = 1.0
if self.idle:
gobject.source_remove(self.idle)
self.idle = gobject.timeout_add(25, self.fade_out)
def fade_out(self):
"""now fade out"""
color = gtk.gdk.color_parse(self.inactive_color)
(red1, green1, blue1) = (color.red, color.green, color.blue)
color = gtk.gdk.color_parse(self.active_color)
(red2, green2, blue2) = (color.red, color.green, color.blue)
red = red1 + int(self.fade_level * (red2 - red1))
green = green1 + int(self.fade_level * (green2 - green1))
blue = blue1 + int(self.fade_level * (blue2 - blue1))
self.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(red, green, blue))
self.fade_level -= 1.0 / (self.fade_duration / 25)
if self.fade_level > 0:
return True
self.idle = 0
return False
class GUI(object):
"""our basic global gui object"""
def __init__(self, pyroom_config, edit_instance):
self.config = pyroom_config.config # FIXME: use pyroom_config itself
# Theme
theme_name = self.config.get('visual', 'theme')
self.theme = Theme(theme_name)
self.status = FadeLabel()
self.edit_instance = edit_instance
# Main window
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_name('PyRoom')
self.window.set_title("PyRoom")
self.window.connect('delete_event', self.delete_event)
self.window.connect('destroy', self.destroy)
self.textbox = gtk.TextView()
self.textbox.connect('scroll-event', self.scroll_event)
self.textbox.set_wrap_mode(gtk.WRAP_WORD)
self.fixed = gtk.Fixed()
self.vbox = gtk.VBox()
self.window.add(self.fixed)
self.fixed.put(self.vbox, 0, 0)
self.boxout = gtk.EventBox()
self.boxout.set_border_width(1)
self.boxin = gtk.EventBox()
self.boxin.set_border_width(1)
self.vbox.pack_start(self.boxout, True, True, 1)
self.boxout.add(self.boxin)
self.scrolled = gtk.ScrolledWindow()
self.boxin.add(self.scrolled)
self.scrolled.add(self.textbox)
self.scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
self.scrolled.show()
self.scrolled.set_property('resize-mode', gtk.RESIZE_PARENT)
self.textbox.set_property('resize-mode', gtk.RESIZE_PARENT)
self.vbox.set_property('resize-mode', gtk.RESIZE_PARENT)
self.vbox.show_all()
# Status
self.hbox = gtk.HBox()
self.hbox.set_spacing(12)
self.hbox.pack_end(self.status, True, True, 0)
self.vbox.pack_end(self.hbox, False, False, 0)
self.status.set_alignment(0.0, 0.5)
self.status.set_justify(gtk.JUSTIFY_LEFT)
self.apply_theme()
def apply_theme(self):
"""immediately apply the theme given in configuration
this has changed from previous versions! Takes no arguments!
Only uses configuration!"""
# text cursor
gtkrc_string = """\
style "pyroom-colored-cursor" {
GtkTextView::cursor-color = '%s'
bg_pixmap[NORMAL] = ""
}
class "GtkWidget" style "pyroom-colored-cursor"
""" % self.theme['foreground']
gtk.rc_parse_string(gtkrc_string)
padding = int(self.theme['padding'])
self.textbox.set_border_width(padding)
# Screen geometry
screen = gtk.gdk.screen_get_default()
root_window = screen.get_root_window()
mouse_x, mouse_y, mouse_mods = root_window.get_pointer()
current_monitor_number = screen.get_monitor_at_point(mouse_x, mouse_y)
monitor_geometry = screen.get_monitor_geometry(current_monitor_number)
(screen_width, screen_height) = (monitor_geometry.width,
monitor_geometry.height)
width_percentage = float(self.theme['width'])
height_percentage = float(self.theme['height'])
# Sizing
self.vbox.set_size_request(
int(width_percentage * screen_width),
int(height_percentage * screen_height)
)
self.fixed.move(self.vbox,
int(((1 - width_percentage) * screen_width) / 2),
int(((1 - height_percentage) * screen_height) / 2)
)
parse_color = lambda x: gtk.gdk.color_parse(self.theme[x])
# Colors
self.window.modify_bg(gtk.STATE_NORMAL, parse_color('background'))
self.boxout.modify_bg(gtk.STATE_NORMAL, parse_color('border'))
self.status.active_color = self.theme['foreground']
self.status.inactive_color = self.theme['background']
self.textbox.modify_bg(gtk.STATE_NORMAL, parse_color('textboxbg'))
self.textbox.modify_base(gtk.STATE_NORMAL, parse_color('textboxbg'))
self.textbox.modify_base(gtk.STATE_SELECTED, parse_color('foreground'))
self.textbox.modify_text(gtk.STATE_NORMAL, parse_color('foreground'))
self.textbox.modify_text(gtk.STATE_SELECTED, parse_color('textboxbg'))
self.textbox.modify_fg(gtk.STATE_NORMAL, parse_color('foreground'))
# Border
if not int(self.config.get('visual', 'showborder')):
self.boxin.set_border_width(0)
self.boxout.set_border_width(0)
else:
self.boxin.set_border_width(1)
self.boxout.set_border_width(1)
# Indent
if self.config.get('visual', 'indent') == '1':
pango_context = self.textbox.get_pango_context()
current_font_size = pango_context.\
get_font_description().\
get_size() / 1024
self.textbox.set_indent(current_font_size * 2)
else:
self.textbox.set_indent(0)
def quit(self):
""" quit pyroom """
gtk.main_quit()
def delete_event(self, widget, event, data=None):
""" Quit """
self.edit_instance.dialog_quit()
return True
def destroy(self, widget, data=None):
""" Quit """
gtk.main_quit()
def scroll_event(self, widget, event):
""" Scroll event dispatcher """
if event.direction == gtk.gdk.SCROLL_UP:
self.scroll_up()
elif event.direction == gtk.gdk.SCROLL_DOWN:
self.scroll_down()
def scroll_down(self):
""" Scroll window down """
adj = self.scrolled.get_vadjustment()
if adj.upper > adj.page_size:
adj.value = min(adj.upper - adj.page_size, adj.value
+ adj.step_increment)
def scroll_up(self):
""" Scroll window up """
adj = self.scrolled.get_vadjustment()
if adj.value > adj.step_increment:
adj.value -= adj.step_increment
else:
adj.value = 0