Python cleanup

This commit is contained in:
Sean Davis 2014-01-25 17:21:53 -05:00
parent 653fc5a108
commit 527fa8cf4c
7 changed files with 359 additions and 280 deletions

View File

@ -3,16 +3,16 @@
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# Copyright (C) 2010 Rick Spencer <rick.spencer@canonical.com>
# Portions of this code are inspired by web_cam_box by Rick Spencer.
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
@ -21,65 +21,69 @@ from locale import gettext as _
import logging
logger = logging.getLogger('mugshot')
from gi.repository import Gtk, GdkX11, GObject, Gst, GstVideo, GdkPixbuf
from gi.repository import Gtk, GObject, Gst, GdkPixbuf
import cairo
import tempfile, os
import tempfile
import os
from mugshot_lib.CameraDialog import CameraDialog
def draw_message(widget, message, ctx):
"""Draw a message (including newlines) vertically centered on a cairo context."""
"""Draw a message (including newlines) vertically centered on a cairo
context."""
split_msg = message.split('\n')
# Get the height and width of the drawing area.
alloc = widget.get_allocation()
height = alloc.height
width = alloc.width
# Make the background black.
ctx.set_source_rgb(0,0,0)
ctx.set_source_rgb(0, 0, 0)
ctx.paint()
# Set the font details.
font_size = 20
font_color = (255,255,255)
font_color = (255, 255, 255)
font_name = "Sans"
row_spacing = 6
left_spacing = 10
# Get start position
message_height = (len(split_msg)*font_size)+len(split_msg)-1-14
current_pos = (height-message_height)/2
message_height = (len(split_msg) * font_size) + len(split_msg) - 15
current_pos = (height - message_height) / 2
# Draw the message to the drawing area.
ctx.set_source_rgb(*font_color)
ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size)
for line in split_msg:
ctx.move_to(left_spacing,current_pos)
ctx.move_to(left_spacing, current_pos)
ctx.show_text(line)
current_pos = current_pos + font_size + row_spacing
class CameraMugshotDialog(CameraDialog):
"""Camera Capturing Dialog"""
__gtype_name__ = "CameraMugshotDialog"
def finish_initializing(self, builder): # pylint: disable=E1002
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the camera dialog"""
super(CameraMugshotDialog, self).finish_initializing(builder)
# Initialize Gst or nothing will work.
Gst.init(None)
# Pack the video widget into the dialog.
vbox = builder.get_object('camera_box')
self.video_window = Gtk.DrawingArea()
self.video_window.connect("realize",self.__on_video_window_realized)
self.video_window.connect("realize", self.__on_video_window_realized)
vbox.pack_start(self.video_window, True, True, 0)
self.video_window.show()
# Prepare the camerabin element.
self.camerabin = Gst.ElementFactory.make("camerabin", "camera-source")
if self.camerabin:
@ -94,55 +98,62 @@ class CameraMugshotDialog(CameraDialog):
else:
devices = []
for device in os.listdir('/dev/'):
if device.startswith('video'): devices.append(device)
logger.error(_('Camera failed to load. Devices: %s') % '; '.join(devices))
self.draw_handler = self.video_window.connect('draw', self.on_failed_draw)
if device.startswith('video'):
devices.append(device)
logger.error(_('Camera failed to load. Devices: %s') %
'; '.join(devices))
self.draw_handler = self.video_window.connect('draw',
self.on_failed_draw)
self.realized = True
# Essential widgets
self.record_button = builder.get_object('camera_record')
self.apply_button = builder.get_object('camera_apply')
# Store the temporary filename to be used.
self.filename = None
self.show_all()
def on_failed_draw(self, widget, ctx):
"""Display a message that the camera failed to load."""
# Translators: Please include newlines, as required to fit the message.
message = _("Sorry, but your camera\nfailed to initialize.")
draw_message(widget, message, ctx)
def on_draw(self, widget, ctx):
"""Display a message that the camera is initializing on first draw.
Afterwards, blank the drawing area to clear the message."""
# Translators: Please include newlines, as required to fit the message.
message = _("Please wait while your\ncamera is initialized.")
draw_message(widget, message, ctx)
# Redefine on_draw to blank the drawing area next time.
def on_draw(self, widget, ctx):
ctx.set_source_rgb(0,0,0)
"""Redefinition of on_draw to blank the drawing area next time."""
ctx.set_source_rgb(0, 0, 0)
ctx.paint()
# Redefine on_draw once more to do nothing else.
def on_draw(self, widget, ctx):
"""Redefinition of on_draw no longer do anything."""
pass
def play(self):
"""Start the camera streaming and display the output. It is necessary
"""Start the camera streaming and display the output. It is necessary
to start the camera playing before using most other functions."""
if not self.realized:
self._set_video_window_id()
if not self.realized:
logger.error(_("Cannot display camera output. Ignoring play command"))
logger.error(_("Cannot display camera output."
"Ignoring play command"))
else:
if self.camerabin:
self.camerabin.set_state(Gst.State.PLAYING)
def pause(self):
"""Pause the camera output. It will cause the image to "freeze".
Use play() to start the camera playing again. Note that calling pause
"""Pause the camera output. It will cause the image to "freeze".
Use play() to start the camera playing again. Note that calling pause
before play may cause errors on certain camera."""
if self.camerabin:
self.camerabin.set_state(Gst.State.PAUSED)
@ -150,11 +161,11 @@ class CameraMugshotDialog(CameraDialog):
def take_picture(self, filename):
"""take_picture - grab a frame from the webcam and save it to
'filename.
If play is not called before take_picture,
an error may occur. If take_picture is called immediately after play,
the camera may not be fully initialized, and an error may occur.
the camera may not be fully initialized, and an error may occur.
Connect to the signal "image-captured" to be alerted when the picture
is saved."""
self.camerabin.set_property("location", filename)
@ -178,18 +189,18 @@ class CameraMugshotDialog(CameraDialog):
# Get the message type.
t = message.type
# Initial load, wait until camera is ready before enabling capture.
if t == Gst.MessageType.ASYNC_DONE:
self.record_button.set_sensitive(True)
if t == Gst.MessageType.ELEMENT:
# Keep the camera working after several pictures are taken.
if message.get_structure().get_name() == "image-captured":
self.camerabin.set_state(Gst.Sate.NULL)
self.camerabin.set_state(Gst.State.PLAYING)
self.emit("image-captured", self.filename)
# Enable interface elements once the images are finished saving.
elif message.get_structure().get_name() == "image-done":
self.apply_button.set_sensitive(True)
@ -199,7 +210,7 @@ class CameraMugshotDialog(CameraDialog):
# Stop the stream if the EOS (end of stream) message is received.
if t == Gst.MessageType.EOS:
self.camerabin.set_state(Gst.State.NULL)
# Capture and report any error received.
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
@ -223,48 +234,49 @@ class CameraMugshotDialog(CameraDialog):
if message_name == "prepare-window-handle":
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_window_handle(self.video_window.get_window().get_xid())
imagesink.set_window_handle(
self.video_window.get_window().get_xid())
def __on_video_window_realized(self, widget, data=None):
"""Internal signal handler, used to set up the xid for the drawing area
"""Internal signal handler, used to set up the xid for the drawing area
in a thread safe manner. Do not call directly."""
self._set_video_window_id()
def _set_video_window_id(self):
"""Set the window ID only if not previously configured."""
if not self.realized and self.video_window.get_window() is not None:
x = self.video_window.get_window().get_xid()
self.video_window.get_window().get_xid()
self.realized = True
def on_camera_record_clicked(self, widget):
"""When the camera record/retry button is clicked:
Record: Pause the video, start the capture, enable apply and retry.
Retry: Restart the video stream."""
# Remove any previous temporary file.
if self.filename and os.path.isfile(self.filename):
if self.filename and os.path.isfile(self.filename):
os.remove(self.filename)
# Retry action.
if self.apply_button.get_sensitive():
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False)
self.play()
# Record (Capture) action.
else:
# Create a new temporary file.
tmpfile = tempfile.NamedTemporaryFile(delete=False)
tmpfile.close()
self.filename = tmpfile.name
# Capture the current image.
self.take_picture(self.filename)
# Set the record button to retry, and disable it until the capture
# finishes.
self.record_button.set_label(_("Retry"))
self.record_button.set_sensitive(False)
def on_camera_apply_clicked(self, widget):
"""When the camera Apply button is clicked, crop the current photo and
emit a signal to let the main application know there is a new file
@ -272,7 +284,7 @@ class CameraMugshotDialog(CameraDialog):
self.center_crop(self.filename)
self.emit("apply", self.filename)
self.hide()
def on_camera_cancel_clicked(self, widget):
"""When the Cancel button is clicked, just hide the dialog."""
self.hide()
@ -285,54 +297,56 @@ class CameraMugshotDialog(CameraDialog):
os.remove(self.filename)
# Clean up the camera before exiting
self.camerabin.set_state(Gst.State.NULL)
def on_camera_mugshot_dialog_hide(self, widget, data=None):
"""When the dialog is hidden, pause the camera recording."""
self.pause()
def on_camera_mugshot_dialog_show(self, widget, data=None):
"""When the dialog is shown, set the record button to record, disable
"""When the dialog is shown, set the record button to record, disable
the apply button, and start the camera."""
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False)
self.show_all()
self.play()
def center_crop(self, filename):
"""Crop the specified file to square dimensions."""
# Load the image into a Pixbuf.
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
# Get the image dimensions.
height = pixbuf.get_height()
width = pixbuf.get_width()
start_x = 0
start_y = 0
# Calculate a balanced center.
if width > height:
start_x = (width-height)/2
start_x = (width - height) / 2
width = height
else:
start_y = (height-width)/2
start_y = (height - width) / 2
height = width
# Create a new cropped pixbuf.
new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height)
# Overwrite the temporary file with our new cropped image.
new_pixbuf.savev(filename, "png", [], [])
def on_camera_mugshot_dialog_delete_event(self, widget, data=None):
"""Override the dialog delete event to just hide the window."""
self.hide()
return True
# Signals used by CameraMugshotDialog:
# image-captured: emitted when the camera is done capturing an image.
# apply: emitted when the apply button has been pressed and there is a new file saved for use.
__gsignals__ = {'image-captured' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
(GObject.TYPE_PYOBJECT,)),
'apply' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,))
}
# apply: emitted when the apply button has been pressed and there is a
# new file saved for use.
__gsignals__ = {'image-captured': (GObject.SIGNAL_RUN_LAST,
GObject.TYPE_NONE,
(GObject.TYPE_PYOBJECT,)),
'apply': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
(GObject.TYPE_STRING,))
}

View File

@ -1,16 +1,16 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
@ -28,7 +28,7 @@ import dbus
import tempfile
from gi.repository import Gtk, Gdk, GdkPixbuf # pylint: disable=E0611
from gi.repository import Gtk, GdkPixbuf # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot')
@ -44,52 +44,64 @@ libreoffice_prefs = os.path.join(home, '.config', 'libreoffice', '4', 'user',
pidgin_prefs = os.path.join(home, '.purple', 'prefs.xml')
faces_dir = '/usr/share/pixmaps/faces/'
def which(command):
'''Use the system command which to get the absolute path for the given
command.'''
command = subprocess.Popen(['which', command], \
command = subprocess.Popen(['which', command],
stdout=subprocess.PIPE).stdout.read().strip()
if command == '':
if command == '':
logger.debug('Command "%s" could not be found.' % command)
return None
return command
def has_running_process(name):
"""Check for a running process, return True if any listings are found."""
command = 'ps -ef | grep " %s" | grep -v "grep" | wc -l' % name
n = subprocess.Popen(command, stdout=subprocess.PIPE,
n = subprocess.Popen(command, stdout=subprocess.PIPE,
shell=True).stdout.read().strip()
return int(n) > 0
def has_gstreamer_camerabin_support():
"""Return True if gstreamer1.0 camerabin element is available."""
process = subprocess.Popen(["gst-inspect-1.0", "camerabin"],
stdout=subprocess.PIPE,
process = subprocess.Popen(["gst-inspect-1.0", "camerabin"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process.communicate()
has_support = process.returncode == 0
if not has_support:
logger.debug('camerabin element unavailable. Do you have gstreamer1.0-plugins-good installed?')
element = 'camerabin'
plugin = 'gstreamer1.0-plugins-good'
logger.debug('%s element unavailable. '
'Do you have %s installed?' % (element, plugin))
return has_support
def has_gstreamer_camerasrc_support():
"""Return True if gstreamer1.0 v4l2src element is available."""
process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"],
stdout=subprocess.PIPE,
process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process.communicate()
has_support = process.returncode == 0
if not has_support:
logger.debug('v4l2src element unavailable. Do you have gstreamer1.0-plugins-good installed?')
element = 'v4l2src'
plugin = 'gstreamer1.0-plugins-good'
logger.debug('%s element unavailable. '
'Do you have %s installed?' % (element, plugin))
return has_support
def get_camera_installed():
"""Return True if /dev/video0 exists."""
if not os.path.exists('/dev/video0'):
logger.debug('Camera not detected at /dev/video0')
return False
return True
def get_has_camera_support():
"""Return True if cameras are fully supported by this application."""
if not get_camera_installed():
@ -102,10 +114,12 @@ def get_has_camera_support():
return False
return True
def detach_cb(menu, widget):
'''Detach a widget from its attached widget.'''
menu.detach()
def get_entry_value(entry_widget):
"""Get the value from one of the Mugshot entries."""
# Get the text from an entry, changing none to ''
@ -113,10 +127,11 @@ def get_entry_value(entry_widget):
if value.lower() == 'none':
value = ''
return value
def get_confirmation_dialog(parent, primary_message, secondary_message,
def get_confirmation_dialog(parent, primary_message, secondary_message,
icon_name=None):
"""Display a confirmation (yes/no) dialog configured with primary and
"""Display a confirmation (yes/no) dialog configured with primary and
secondary messages, as well as a custom icon if requested."""
dialog = Gtk.MessageDialog(parent, flags=0, type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
@ -129,7 +144,8 @@ def get_confirmation_dialog(parent, primary_message, secondary_message,
response = dialog.run()
dialog.destroy()
return response == Gtk.ResponseType.YES
def menu_position(self, menu, data=None, something_else=None):
'''Position a menu at the bottom of its attached widget'''
widget = menu.get_attach_widget()
@ -141,15 +157,17 @@ def menu_position(self, menu, data=None, something_else=None):
y = window_pos[1] + allocation.y + allocation.height
return (x, y, True)
# See mugshot_lib.Window.py for more details about how this class works
class MugshotWindow(Window):
"""Mugshot GtkWindow"""
__gtype_name__ = "MugshotWindow"
def finish_initializing(self, builder): # pylint: disable=E1002
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
super(MugshotWindow, self).finish_initializing(builder)
self.set_wmclass("Mugshot", "Mugshot")
self.CameraDialog = CameraMugshotDialog
# User Image widgets
@ -159,9 +177,9 @@ class MugshotWindow(Window):
self.image_menu.attach_to_widget(self.image_button, detach_cb)
self.image_from_camera = builder.get_object('image_from_camera')
image_from_browse = builder.get_object('image_from_browse')
image_from_browse.set_visible( os.path.exists(faces_dir) and \
len(os.listdir(faces_dir)) > 0 )
image_from_browse.set_visible(os.path.exists(faces_dir) and
len(os.listdir(faces_dir)) > 0)
# Entry widgets (chfn)
self.first_name_entry = builder.get_object('first_name')
self.last_name_entry = builder.get_object('last_name')
@ -170,11 +188,11 @@ class MugshotWindow(Window):
self.home_phone_entry = builder.get_object('home_phone')
self.email_entry = builder.get_object('email')
self.fax_entry = builder.get_object('fax')
# Stock photo browser
self.stock_browser = builder.get_object('stock_browser')
self.iconview = builder.get_object('stock_iconview')
# File Chooser Dialog
self.chooser = builder.get_object('filechooserdialog')
self.crop_center = builder.get_object('crop_center')
@ -186,12 +204,12 @@ class MugshotWindow(Window):
image_filter.set_name('Images')
image_filter.add_mime_type('image/*')
self.chooser.add_filter(image_filter)
self.tmpfile = None
# Populate all of the widgets.
self.init_user_details()
def init_user_details(self):
"""Initialize the user details entries and variables."""
# Check for .face and set profile image.
@ -202,7 +220,7 @@ class MugshotWindow(Window):
else:
self.set_user_image(None)
self.updated_image = None
# Search /etc/passwd for the current user's details.
logger.debug('Getting user details from /etc/passwd')
for line in open('/etc/passwd', 'r'):
@ -211,7 +229,7 @@ class MugshotWindow(Window):
details = line.split(':')[4]
name, office, office_phone, home_phone = details.split(',', 3)
break
# Expand the user's fullname into first, last, and initials.
try:
first_name, last_name = name.split(' ', 1)
@ -220,25 +238,27 @@ class MugshotWindow(Window):
first_name = name
last_name = ''
initials = first_name[0]
# If the variables are defined as 'none', use blank for cleanliness.
if home_phone == 'none': home_phone = ''
if office_phone == 'none': office_phone = ''
if home_phone == 'none':
home_phone = ''
if office_phone == 'none':
office_phone = ''
# Get dconf settings
logger.debug('Getting initials, email, and fax from dconf')
if self.settings['initials'] != '':
initials = self.settings['initials']
email = self.settings['email']
fax = self.settings['fax']
# Set the class variables
self.first_name = first_name
self.last_name = last_name
self.initials = initials
self.home_phone = home_phone
self.office_phone = office_phone
# Populate the GtkEntries.
logger.debug('Populating entries')
self.first_name_entry.set_text(self.first_name)
@ -248,7 +268,7 @@ class MugshotWindow(Window):
self.home_phone_entry.set_text(self.home_phone)
self.email_entry.set_text(email)
self.fax_entry.set_text(fax)
# = Mugshot Window ======================================================= #
def set_user_image(self, filename=None):
"""Scale and set the user profile image."""
@ -264,83 +284,84 @@ class MugshotWindow(Window):
"""Allow only numbers and + in phone entry fields."""
text = entry.get_text().strip()
entry.set_text(''.join([i for i in text if i in '+0123456789']))
def on_apply_button_clicked(self, widget):
"""When the window Apply button is clicked, commit any relevant
"""When the window Apply button is clicked, commit any relevant
changes."""
logger.debug('Applying changes...')
if self.get_chfn_details_updated():
returns = self.save_chfn_details()
self.save_chfn_details()
if self.get_libreoffice_details_updated():
self.set_libreoffice_data()
if self.updated_image:
self.save_image()
self.save_gsettings()
self.destroy()
def save_gsettings(self):
"""Save details to dconf (the ones not tracked by /etc/passwd)"""
logger.debug('Saving details to dconf: /apps/mugshot')
self.settings.set_string('initials',
self.settings.set_string('initials',
get_entry_value(self.initials_entry))
self.settings.set_string('email', get_entry_value(self.email_entry))
self.settings.set_string('fax', get_entry_value(self.fax_entry))
def entry_focus_next(self, widget):
"""Focus the next available entry when pressing Enter."""
logger.debug('Entry activated, focusing next widget.')
vbox = widget.get_parent().get_parent().get_parent().get_parent()
vbox.child_focus(Gtk.DirectionType.TAB_FORWARD)
def on_cancel_button_clicked(self, widget):
"""When the window cancel button is clicked, close the program."""
logger.debug('Cancel clicked, goodbye.')
self.destroy()
# = Image Button and Menu ================================================ #
def on_image_button_clicked(self, widget):
"""When the menu button is clicked, display the photo menu."""
if widget.get_active():
logger.debug('Show photo menu')
self.image_from_camera.set_visible(get_has_camera_support())
self.image_menu.popup(None, None, menu_position,
self.image_menu, 3,
self.image_menu.popup(None, None, menu_position,
self.image_menu, 3,
Gtk.get_current_event_time())
def on_image_menu_hide(self, widget):
"""Untoggle the image button when the menu is hidden."""
self.image_button.set_active(False)
def on_camera_dialog_apply(self, widget, data=None):
"""Commit changes when apply is clicked."""
self.updated_image = data
self.set_user_image(data)
def save_image(self):
"""Copy the updated image filename to ~/.face"""
# Check if the image has been updated.
if not self.updated_image:
logger.debug('Photo not updated, not saving changes.')
return False
face = os.path.expanduser('~/.face')
# If the .face file already exists, remove it first.
logger.debug('Photo updated, saving changes.')
if os.path.isfile(face):
os.remove(face)
# Copy the new file to ~/.face
shutil.copyfile(self.updated_image, face)
self.set_pidgin_buddyicon(face)
self.updated_image = None
return True
def set_pidgin_buddyicon(self, filename=None):
"""Sets the pidgin buddyicon to filename (usually ~/.face).
If pidgin is running, use the dbus interface, otherwise directly modify
the XML file."""
if not os.path.exists(pidgin_prefs):
@ -358,19 +379,19 @@ class MugshotWindow(Window):
self.set_pidgin_buddyicon_xml(filename)
else:
logger.debug('Reject: Not updating pidgin buddy icon')
def set_pidgin_buddyicon_dbus(self, filename=None):
"""Set the pidgin buddy icon via dbus."""
logger.debug('Updating pidgin buddy icon via dbus')
bus = dbus.SessionBus()
obj = bus.get_object("im.pidgin.purple.PurpleService",
obj = bus.get_object("im.pidgin.purple.PurpleService",
"/im/pidgin/purple/PurpleObject")
purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
# To make the change instantly visible, set the icon to none first.
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', '')
if filename:
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', filename)
def set_pidgin_buddyicon_xml(self, filename=None):
"""Set the buddyicon used by pidgin to filename (via the xml file)."""
# This is hacky, but a working implementation for now...
@ -380,9 +401,9 @@ class MugshotWindow(Window):
if os.path.isfile(prefs_file):
for line in open(prefs_file):
if '<pref name=\'buddyicon\'' in line:
new = line.split('value=')[0]
new = line.split('value=')[0]
if filename:
new = new + 'value=\'%s\'/>\n' % filename
new = new + 'value=\'%s\'/>\n' % filename
else:
new = new + 'value=\'\'/>\n'
tmp_buffer.append(new)
@ -392,7 +413,7 @@ class MugshotWindow(Window):
for line in tmp_buffer:
write_prefs.write(line)
write_prefs.close()
# = chfn functions ============================================ #
def get_chfn_details_updated(self):
"""Return True if chfn-related details have been modified."""
@ -405,25 +426,25 @@ class MugshotWindow(Window):
return True
logger.debug('chfn details have NOT been modified.')
return False
def save_chfn_details(self):
"""Commit changes to chfn-related details. For full name, changes must
be performed as root. Other changes are done with the user password.
Return exit codes for 1) full name changes and 2) home/work phone
changes.
e.g. [0, 0] (both passed)"""
return_codes = []
# Get the user's password
password = self.get_password()
if not password:
return return_codes
username = os.getenv('USER')
chfn = which('chfn')
# Get each of the updated values.
first_name = get_entry_value(self.first_name_entry)
last_name = get_entry_value(self.last_name_entry)
@ -435,7 +456,7 @@ class MugshotWindow(Window):
home_phone = get_entry_value(self.home_phone_entry)
if home_phone == '':
home_phone = 'none'
# Full name can only be modified by root. Try using sudo to modify.
logger.debug('Attempting to set fullname with sudo chfn')
child = pexpect.spawn('sudo %s %s' % (chfn, username))
@ -449,9 +470,9 @@ class MugshotWindow(Window):
child.sendline('')
except pexpect.TIMEOUT:
# Password was incorrect, or sudo rights not granted
logger.debug('Timeout reached, password was incorrect or sudo ' \
logger.debug('Timeout reached, password was incorrect or sudo '
'rights not granted.')
pass
pass
child.close()
if child.exitstatus == 0:
self.first_name = first_name
@ -479,7 +500,7 @@ class MugshotWindow(Window):
self.home_phone = home_phone
return_codes.append(child.exitstatus)
return return_codes
# = LibreOffice ========================================================== #
def get_libreoffice_details_updated(self):
"""Return True if LibreOffice settings need to be updated."""
@ -506,14 +527,14 @@ class MugshotWindow(Window):
return True
logger.debug('LibreOffice details do not need to be updated.')
return False
def get_libreoffice_data(self):
"""Get each of the preferences from the LibreOffice registymodifications
preferences file.
Return a dict with the details."""
prefs_file = libreoffice_prefs
data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '',
data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '',
'home_phone': '', 'office_phone': '', 'fax': ''}
if os.path.isfile(prefs_file):
logger.debug('Getting settings from %s' % prefs_file)
@ -545,7 +566,7 @@ class MugshotWindow(Window):
else:
pass
return data
def set_libreoffice_data(self):
"""Update the LibreOffice registymodifications preferences file."""
prefs_file = libreoffice_prefs
@ -622,40 +643,61 @@ class MugshotWindow(Window):
open_prefs = open(prefs_file, 'w')
for line in tmp_buffer:
open_prefs.write(line)
if not first_name_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="givenname" oor:op="fuse"><value>%s</value></prop></item>\n' % first_name
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="givenname" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % first_name
open_prefs.write(string)
if not last_name_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="sn" oor:op="fuse"><value>%s</value></prop></item>\n' % last_name
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="sn" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % last_name
open_prefs.write(string)
if not initials_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="initials" oor:op="fuse"><value>%s</value></prop></item>\n' % initials
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="initials" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % initials
open_prefs.write(string)
if not email_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="mail" oor:op="fuse"><value>%s</value></prop></item>\n' % email
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="mail" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % email
open_prefs.write(string)
if not home_phone_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="homephone" oor:op="fuse"><value>%s</value></prop></item>\n' % home_phone
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="homephone" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % home_phone
open_prefs.write(string)
if not office_phone_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="telephonenumber" oor:op="fuse"><value>%s</value></prop></item>\n' % office_phone
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="telephonenumber" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % office_phone
open_prefs.write(string)
if not fax_updated:
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="facsimiletelephonenumber" oor:op="fuse"><value>%s</value></prop></item>\n' % fax
string = \
'<item oor:path="/org.openoffice.UserProfile/Data">'
'<prop oor:name="facsimiletelephonenumber" oor:op="fuse">'
'<value>%s</value></prop></item>\n' % fax
open_prefs.write(string)
open_prefs.write('</oor:items>')
open_prefs.close()
else:
logger.debug('Reject: Not updating.')
# = Stock Browser ======================================================== #
def on_image_from_stock_activate(self, widget):
"""When the 'Select image from stock' menu item is clicked, load and
"""When the 'Select image from stock' menu item is clicked, load and
display the stock photo browser."""
self.load_stock_browser()
self.stock_browser.show_all()
def load_stock_browser(self):
"""Load the stock photo browser."""
# Check if the photos have already been loaded.
@ -663,7 +705,7 @@ class MugshotWindow(Window):
if len(model) != 0:
logger.debug("Stock browser already loaded.")
return
# If they have not, load each photo from /usr/share/pixmaps/faces.
logger.debug("Loading stock browser photos.")
for filename in os.listdir('/usr/share/pixmaps/faces'):
@ -672,24 +714,24 @@ class MugshotWindow(Window):
pixbuf = GdkPixbuf.Pixbuf.new_from_file(full_path)
scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER)
model.append([full_path, scaled])
def on_stock_iconview_selection_changed(self, widget):
"""Enable stock submission only when an item is selected."""
selected_items = self.iconview.get_selected_items()
self.builder.get_object('stock_ok').set_sensitive(
len(selected_items) > 0)
def on_stock_browser_delete_event(self, widget, event):
"""Hide the stock browser instead of deleting it."""
widget.hide()
return True
def on_stock_cancel_clicked(self, widget):
"""Hide the stock browser when Cancel is clicked."""
self.stock_browser.hide()
def on_stock_ok_clicked(self, widget):
"""When the stock browser OK button is clicked, get the currently
"""When the stock browser OK button is clicked, get the currently
selected photo and set it to the user profile image."""
selected_items = self.iconview.get_selected_items()
if len(selected_items) != 0:
@ -697,16 +739,16 @@ class MugshotWindow(Window):
path = int(selected_items[0].to_string())
filename = self.iconview.get_model()[path][0]
logger.debug("Selected %s" % filename)
# Update variables and widgets, then hide.
self.set_user_image(filename)
self.updated_image = filename
self.stock_browser.hide()
def on_stock_iconview_item_activated(self, widget, path):
"""Allow selecting a stock photo with Enter."""
self.on_stock_ok_clicked(widget)
# = Image Browser ======================================================== #
def on_image_from_browse_activate(self, widget):
"""Browse for a user profile image."""
@ -719,12 +761,14 @@ class MugshotWindow(Window):
self.tmpfile = tempfile.NamedTemporaryFile(delete=False)
self.tmpfile.close()
self.updated_image = self.tmpfile.name
self.filechooser_preview_pixbuf.savev(self.updated_image, "png", [], [])
self.filechooser_preview_pixbuf.savev(self.updated_image, "png",
[], [])
logger.debug("Selected %s" % self.updated_image)
self.set_user_image(self.updated_image)
self.chooser.hide()
def on_filechooserdialog_update_preview(self, widget):
"""Update the preview image used in the file chooser."""
filename = widget.get_filename()
if not filename:
self.file_chooser_preview.set_from_icon_name('folder', 128)
@ -733,47 +777,50 @@ class MugshotWindow(Window):
self.file_chooser_preview.set_from_icon_name('folder', 128)
return
filechooser_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
# Get the image dimensions.
height = filechooser_pixbuf.get_height()
width = filechooser_pixbuf.get_width()
start_x = 0
start_y = 0
if self.crop_center.get_active():
# Calculate a balanced center.
if width > height:
start_x = (width-height)/2
start_x = (width - height) / 2
width = height
else:
start_y = (height-width)/2
start_y = (height - width) / 2
height = width
elif self.crop_left.get_active():
start_x = 0
if width > height:
width = height
else:
start_y = (height-width)/2
start_y = (height - width) / 2
height = width
elif self.crop_right.get_active():
if width > height:
start_x = width-height
start_x = width - height
width = height
else:
start_y = (height-width)/2
start_y = (height - width) / 2
height = width
# Create a new cropped pixbuf.
self.filechooser_preview_pixbuf = filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height)
scaled = self.filechooser_preview_pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
self.filechooser_preview_pixbuf = \
filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height)
scaled = self.filechooser_preview_pixbuf.scale_simple(128, 128,
GdkPixbuf.InterpType.HYPER)
self.file_chooser_preview.set_from_pixbuf(scaled)
def on_crop_changed(self, widget, data=None):
"""Update the preview image when crop style is modified."""
if widget.get_active():
self.on_filechooserdialog_update_preview(self.chooser)
# = Password Entry ======================================================= #
def get_password(self):
"""Display a password dialog for authenticating to sudo and chfn."""
@ -789,9 +836,8 @@ class MugshotWindow(Window):
return pw
logger.debug("Cancelled")
return None
def on_password_entry_changed(self, widget):
"""Enable password submission only when password is not blank."""
self.builder.get_object('password_ok').set_sensitive(
len(widget.get_text()) > 0)

View File

@ -1,16 +1,16 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
@ -18,7 +18,7 @@
'''Enhances builder connections, provides object to access glade objects'''
from gi.repository import GObject, Gtk # pylint: disable=E0611
from gi.repository import GObject, Gtk # pylint: disable=E0611
import inspect
import functools
@ -46,6 +46,7 @@ class Builder(Gtk.Builder):
'''
def __init__(self):
"""Initialize the builder."""
Gtk.Builder.__init__(self)
self.widgets = {}
self.glade_handler_dict = {}
@ -120,7 +121,7 @@ class Builder(Gtk.Builder):
connection_dict = {}
connection_dict.update(self.glade_handler_dict)
connection_dict.update(callback_handler_dict)
for item in connection_dict.items():
for item in list(connection_dict.items()):
if item[1] is None:
# the handler is missing so reroute to default_handler
handler = functools.partial(
@ -166,17 +167,19 @@ class Builder(Gtk.Builder):
class UiFactory():
''' provides an object with attributes as glade widgets'''
def __init__(self, widget_dict):
"""Initialize the UiFactory."""
self._widget_dict = widget_dict
for (widget_name, widget) in widget_dict.items():
for (widget_name, widget) in list(widget_dict.items()):
setattr(self, widget_name, widget)
# Mangle any non-usable names (like with spaces or dashes)
# into pythonic ones
cannot_message = """cannot bind ui.%s, name already exists
consider using a pythonic name instead of design name '%s'"""
consider_message = """consider using a pythonic name instead of design name '%s'"""
for (widget_name, widget) in widget_dict.items():
consider_message = """consider using a pythonic name instead of
design name '%s'"""
for (widget_name, widget) in list(widget_dict.items()):
pyname = make_pyname(widget_name)
if pyname != widget_name:
if hasattr(self, pyname):
@ -187,7 +190,7 @@ class UiFactory():
def iterator():
'''Support 'for o in self' '''
return iter(widget_dict.values())
return iter(list(widget_dict.values()))
setattr(self, '__iter__', iterator)
def __getitem__(self, name):
@ -208,10 +211,11 @@ def make_pyname(name):
return pyname
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
# need to reimplement inspect.getmembers. GObject introspection doesn't
# play nice with it.
def getmembers(obj, check):
"""Reimplementation of getmembers"""
members = []
for k in dir(obj):
try:
@ -260,7 +264,7 @@ def auto_connect_by_name(callback_obj, builder):
callback_handler_dict = dict_from_callback_obj(callback_obj)
for item in builder.widgets.items():
for item in list(builder.widgets.items()):
(widget_name, widget) = item
signal_ids = []
try:
@ -296,7 +300,7 @@ def do_connect(item, signal_name, handler_names,
widget_name, widget = item
for handler_name in handler_names:
target = handler_name in callback_handler_dict.keys()
target = handler_name in list(callback_handler_dict.keys())
connection = (widget_name, signal_name, handler_name)
duplicate = connection in connections
if target and not duplicate:
@ -312,7 +316,7 @@ def log_unconnected_functions(callback_handler_dict, connections):
connected_functions = [x[2] for x in connections]
handler_names = callback_handler_dict.keys()
handler_names = list(callback_handler_dict.keys())
unconnected = [x for x in handler_names if x.startswith('on_')]
for handler_name in connected_functions:

View File

@ -1,34 +1,36 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
from gi.repository import Gtk # pylint: disable=E0611
from gi.repository import Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot_lib')
from . helpers import get_builder, show_uri, get_help_uri
class CameraDialog(Gtk.Dialog):
"""Camera Dialog"""
__gtype_name__ = "CameraDialog"
def __new__(cls):
"""Special static method that's automatically called by Python when
"""Special static method that's automatically called by Python when
constructing a new instance of this class.
Returns a fully instantiated PreferencesDialog object.
"""
builder = get_builder('CameraMugshotDialog')
@ -43,7 +45,7 @@ class CameraDialog(Gtk.Dialog):
and creating a PreferencesDialog object with it in order to
finish initializing the start of the new PerferencesMugshotDialog
instance.
Put your initialization code in here and leave __init__ undefined.
"""
@ -54,8 +56,9 @@ class CameraDialog(Gtk.Dialog):
# code for other initialization actions should be added here
def on_btn_close_clicked(self, widget, data=None):
"""Destroy the dialog when closed."""
self.destroy()
def on_btn_help_clicked(self, widget, data=None):
"""Show the help dialog when Help is clicked."""
show_uri(self, "ghelp:%s" % get_help_uri('preferences'))

View File

@ -1,22 +1,22 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
from gi.repository import Gio, Gtk # pylint: disable=E0611
from gi.repository import Gio, Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot_lib')
@ -24,12 +24,13 @@ import os
from . helpers import get_builder, show_uri, get_help_uri
# This class is meant to be subclassed by MugshotWindow. It provides
# common functions and some boilerplate.
class Window(Gtk.Window):
"""This class is meant to be subclassed by MugshotWindow. It provides
common functions and some boilerplate."""
__gtype_name__ = "Window"
# To construct a new instance of this method, the following notable
# To construct a new instance of this method, the following notable
# methods are called in this order:
# __new__(cls)
# __init__(self)
@ -38,11 +39,11 @@ class Window(Gtk.Window):
#
# For this reason, it's recommended you leave __init__ empty and put
# your initialization code in finish_initializing
def __new__(cls):
"""Special static method that's automatically called by Python when
"""Special static method that's automatically called by Python when
constructing a new instance of this class.
Returns a fully instantiated BaseMugshotWindow object.
"""
builder = get_builder('MugshotWindow')
@ -60,15 +61,16 @@ class Window(Gtk.Window):
# Get a reference to the builder and set up the signals.
self.builder = builder
self.ui = builder.get_ui(self, True)
self.CameraDialog = None # class
self.camera_dialog = None # instance
self.CameraDialog = None # class
self.camera_dialog = None # instance
self.settings = Gio.Settings("apps.mugshot")
self.settings.connect('changed', self.on_preferences_changed)
def on_help_activate(self, widget, data=None):
"""Show the Help documentation when Help is clicked."""
show_uri(self, "ghelp:%s" % get_help_uri())
def on_menu_camera_activate(self, widget, data=None):
"""Display the camera window for mugshot."""
if self.camera_dialog is not None:
@ -76,8 +78,7 @@ class Window(Gtk.Window):
self.camera_dialog.show()
elif self.CameraDialog is not None:
logger.debug('create new camera_dialog')
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
#self.camera_dialog.connect('destroy', self.on_camera_dialog_destroyed)
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
self.camera_dialog.connect('apply', self.on_camera_dialog_apply)
self.camera_dialog.show()
@ -89,5 +90,6 @@ class Window(Gtk.Window):
Gtk.main_quit()
def on_preferences_changed(self, settings, key, data=None):
logger.debug('preference changed: %s = %s' % (key, str(settings.get_value(key))))
"""Log preference updates."""
logger.debug('preference changed: %s = %s' %
(key, str(settings.get_value(key))))

View File

@ -1,16 +1,16 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
@ -23,12 +23,11 @@ import os
from . mugshotconfig import get_data_file
from . Builder import Builder
from locale import gettext as _
def get_builder(builder_file_name):
"""Return a fully-instantiated Gtk.Builder instance from specified ui
"""Return a fully-instantiated Gtk.Builder instance from specified ui
file
:param builder_file_name: The name of the builder file, without extension.
Assumed to be in the 'ui' directory under the data path.
"""
@ -43,25 +42,31 @@ def get_builder(builder_file_name):
return builder
# Owais Lone : To get quick access to icons and stuff.
def get_media_file(media_file_name):
"""Retrieve the filename for the specified file."""
media_filename = get_data_file('media', '%s' % (media_file_name,))
if not os.path.exists(media_filename):
media_filename = None
return "file:///"+media_filename
return "file:///" + media_filename
class NullHandler(logging.Handler):
"""Handle NULL"""
def emit(self, record):
"""Do not emit anything."""
pass
def set_up_logging(opts):
"""Set up the logging formatter."""
# add a handler to prevent basicConfig
root = logging.getLogger()
null_handler = NullHandler()
root.addHandler(null_handler)
formatter = logging.Formatter("%(levelname)s:%(name)s: %(funcName)s() '%(message)s'")
formatter = logging.Formatter("%(levelname)s:%(name)s:"
" %(funcName)s() '%(message)s'")
logger = logging.getLogger('mugshot')
logger_sh = logging.StreamHandler()
@ -80,7 +85,9 @@ def set_up_logging(opts):
if opts.verbose > 1:
lib_logger.setLevel(logging.DEBUG)
def get_help_uri(page=None):
"""Get the URI to be used for Help."""
# help_uri from source tree - default language
here = os.path.dirname(__file__)
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
@ -95,11 +102,14 @@ def get_help_uri(page=None):
return help_uri
def show_uri(parent, link):
from gi.repository import Gtk # pylint: disable=E0611
"""Open the URI."""
from gi.repository import Gtk # pylint: disable=E0611
screen = parent.get_screen()
Gtk.show_uri(screen, link, Gtk.get_current_event_time())
def alias(alternative_function_name):
'''see http://www.drdobbs.com/web-development/184406073#l9'''
def decorator(function):

View File

@ -1,16 +1,16 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE
@ -30,7 +30,6 @@ __version__ = '0.1'
import os
from locale import gettext as _
class project_path_not_found(Exception):
"""Raised when we can't find the project directory."""
@ -66,4 +65,5 @@ def get_data_path():
def get_version():
"""Return the program version."""
return __version__