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) 2013 Sean Davis <smd.seandavis@gmail.com>
# Copyright (C) 2010 Rick Spencer <rick.spencer@canonical.com> # Copyright (C) 2010 Rick Spencer <rick.spencer@canonical.com>
# Portions of this code are inspired by web_cam_box by Rick Spencer. # 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 # 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 # under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation. # by the Free Software Foundation.
# #
# This program is distributed in the hope that it will be useful, but # This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of # WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details. # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE ### END LICENSE
@ -21,65 +21,69 @@ from locale import gettext as _
import logging import logging
logger = logging.getLogger('mugshot') 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 cairo
import tempfile, os import tempfile
import os
from mugshot_lib.CameraDialog import CameraDialog from mugshot_lib.CameraDialog import CameraDialog
def draw_message(widget, message, ctx): 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') split_msg = message.split('\n')
# Get the height and width of the drawing area. # Get the height and width of the drawing area.
alloc = widget.get_allocation() alloc = widget.get_allocation()
height = alloc.height height = alloc.height
width = alloc.width
# Make the background black. # Make the background black.
ctx.set_source_rgb(0,0,0) ctx.set_source_rgb(0, 0, 0)
ctx.paint() ctx.paint()
# Set the font details. # Set the font details.
font_size = 20 font_size = 20
font_color = (255,255,255) font_color = (255, 255, 255)
font_name = "Sans" font_name = "Sans"
row_spacing = 6 row_spacing = 6
left_spacing = 10 left_spacing = 10
# Get start position # Get start position
message_height = (len(split_msg)*font_size)+len(split_msg)-1-14 message_height = (len(split_msg) * font_size) + len(split_msg) - 15
current_pos = (height-message_height)/2 current_pos = (height - message_height) / 2
# Draw the message to the drawing area. # Draw the message to the drawing area.
ctx.set_source_rgb(*font_color) ctx.set_source_rgb(*font_color)
ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL, ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL) cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size) ctx.set_font_size(font_size)
for line in split_msg: for line in split_msg:
ctx.move_to(left_spacing,current_pos) ctx.move_to(left_spacing, current_pos)
ctx.show_text(line) ctx.show_text(line)
current_pos = current_pos + font_size + row_spacing current_pos = current_pos + font_size + row_spacing
class CameraMugshotDialog(CameraDialog): class CameraMugshotDialog(CameraDialog):
"""Camera Capturing Dialog"""
__gtype_name__ = "CameraMugshotDialog" __gtype_name__ = "CameraMugshotDialog"
def finish_initializing(self, builder): # pylint: disable=E1002 def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the camera dialog""" """Set up the camera dialog"""
super(CameraMugshotDialog, self).finish_initializing(builder) super(CameraMugshotDialog, self).finish_initializing(builder)
# Initialize Gst or nothing will work. # Initialize Gst or nothing will work.
Gst.init(None) Gst.init(None)
# Pack the video widget into the dialog. # Pack the video widget into the dialog.
vbox = builder.get_object('camera_box') vbox = builder.get_object('camera_box')
self.video_window = Gtk.DrawingArea() 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) vbox.pack_start(self.video_window, True, True, 0)
self.video_window.show() self.video_window.show()
# Prepare the camerabin element. # Prepare the camerabin element.
self.camerabin = Gst.ElementFactory.make("camerabin", "camera-source") self.camerabin = Gst.ElementFactory.make("camerabin", "camera-source")
if self.camerabin: if self.camerabin:
@ -94,55 +98,62 @@ class CameraMugshotDialog(CameraDialog):
else: else:
devices = [] devices = []
for device in os.listdir('/dev/'): for device in os.listdir('/dev/'):
if device.startswith('video'): devices.append(device) if device.startswith('video'):
logger.error(_('Camera failed to load. Devices: %s') % '; '.join(devices)) devices.append(device)
self.draw_handler = self.video_window.connect('draw', self.on_failed_draw) 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 self.realized = True
# Essential widgets # Essential widgets
self.record_button = builder.get_object('camera_record') self.record_button = builder.get_object('camera_record')
self.apply_button = builder.get_object('camera_apply') self.apply_button = builder.get_object('camera_apply')
# Store the temporary filename to be used. # Store the temporary filename to be used.
self.filename = None self.filename = None
self.show_all() self.show_all()
def on_failed_draw(self, widget, ctx): def on_failed_draw(self, widget, ctx):
"""Display a message that the camera failed to load.""" """Display a message that the camera failed to load."""
# Translators: Please include newlines, as required to fit the message. # Translators: Please include newlines, as required to fit the message.
message = _("Sorry, but your camera\nfailed to initialize.") message = _("Sorry, but your camera\nfailed to initialize.")
draw_message(widget, message, ctx) draw_message(widget, message, ctx)
def on_draw(self, widget, ctx): def on_draw(self, widget, ctx):
"""Display a message that the camera is initializing on first draw. """Display a message that the camera is initializing on first draw.
Afterwards, blank the drawing area to clear the message.""" Afterwards, blank the drawing area to clear the message."""
# Translators: Please include newlines, as required to fit the message. # Translators: Please include newlines, as required to fit the message.
message = _("Please wait while your\ncamera is initialized.") message = _("Please wait while your\ncamera is initialized.")
draw_message(widget, message, ctx) draw_message(widget, message, ctx)
# Redefine on_draw to blank the drawing area next time. # Redefine on_draw to blank the drawing area next time.
def on_draw(self, widget, ctx): 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() ctx.paint()
# Redefine on_draw once more to do nothing else. # Redefine on_draw once more to do nothing else.
def on_draw(self, widget, ctx): def on_draw(self, widget, ctx):
"""Redefinition of on_draw no longer do anything."""
pass pass
def play(self): 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.""" to start the camera playing before using most other functions."""
if not self.realized: if not self.realized:
self._set_video_window_id() self._set_video_window_id()
if not self.realized: if not self.realized:
logger.error(_("Cannot display camera output. Ignoring play command")) logger.error(_("Cannot display camera output."
"Ignoring play command"))
else: else:
if self.camerabin: if self.camerabin:
self.camerabin.set_state(Gst.State.PLAYING) self.camerabin.set_state(Gst.State.PLAYING)
def pause(self): def pause(self):
"""Pause the camera output. It will cause the image to "freeze". """Pause the camera output. It will cause the image to "freeze".
Use play() to start the camera playing again. Note that calling pause Use play() to start the camera playing again. Note that calling pause
before play may cause errors on certain camera.""" before play may cause errors on certain camera."""
if self.camerabin: if self.camerabin:
self.camerabin.set_state(Gst.State.PAUSED) self.camerabin.set_state(Gst.State.PAUSED)
@ -150,11 +161,11 @@ class CameraMugshotDialog(CameraDialog):
def take_picture(self, filename): def take_picture(self, filename):
"""take_picture - grab a frame from the webcam and save it to """take_picture - grab a frame from the webcam and save it to
'filename. 'filename.
If play is not called before take_picture, If play is not called before take_picture,
an error may occur. If take_picture is called immediately after play, 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 Connect to the signal "image-captured" to be alerted when the picture
is saved.""" is saved."""
self.camerabin.set_property("location", filename) self.camerabin.set_property("location", filename)
@ -178,18 +189,18 @@ class CameraMugshotDialog(CameraDialog):
# Get the message type. # Get the message type.
t = message.type t = message.type
# Initial load, wait until camera is ready before enabling capture. # Initial load, wait until camera is ready before enabling capture.
if t == Gst.MessageType.ASYNC_DONE: if t == Gst.MessageType.ASYNC_DONE:
self.record_button.set_sensitive(True) self.record_button.set_sensitive(True)
if t == Gst.MessageType.ELEMENT: if t == Gst.MessageType.ELEMENT:
# Keep the camera working after several pictures are taken. # Keep the camera working after several pictures are taken.
if message.get_structure().get_name() == "image-captured": if message.get_structure().get_name() == "image-captured":
self.camerabin.set_state(Gst.Sate.NULL) self.camerabin.set_state(Gst.Sate.NULL)
self.camerabin.set_state(Gst.State.PLAYING) self.camerabin.set_state(Gst.State.PLAYING)
self.emit("image-captured", self.filename) self.emit("image-captured", self.filename)
# Enable interface elements once the images are finished saving. # Enable interface elements once the images are finished saving.
elif message.get_structure().get_name() == "image-done": elif message.get_structure().get_name() == "image-done":
self.apply_button.set_sensitive(True) 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. # Stop the stream if the EOS (end of stream) message is received.
if t == Gst.MessageType.EOS: if t == Gst.MessageType.EOS:
self.camerabin.set_state(Gst.State.NULL) self.camerabin.set_state(Gst.State.NULL)
# Capture and report any error received. # Capture and report any error received.
elif t == Gst.MessageType.ERROR: elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error() err, debug = message.parse_error()
@ -223,48 +234,49 @@ class CameraMugshotDialog(CameraDialog):
if message_name == "prepare-window-handle": if message_name == "prepare-window-handle":
imagesink = message.src imagesink = message.src
imagesink.set_property("force-aspect-ratio", True) 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): 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.""" in a thread safe manner. Do not call directly."""
self._set_video_window_id() self._set_video_window_id()
def _set_video_window_id(self): def _set_video_window_id(self):
"""Set the window ID only if not previously configured.""" """Set the window ID only if not previously configured."""
if not self.realized and self.video_window.get_window() is not None: 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 self.realized = True
def on_camera_record_clicked(self, widget): def on_camera_record_clicked(self, widget):
"""When the camera record/retry button is clicked: """When the camera record/retry button is clicked:
Record: Pause the video, start the capture, enable apply and retry. Record: Pause the video, start the capture, enable apply and retry.
Retry: Restart the video stream.""" Retry: Restart the video stream."""
# Remove any previous temporary file. # 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) os.remove(self.filename)
# Retry action. # Retry action.
if self.apply_button.get_sensitive(): if self.apply_button.get_sensitive():
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD) self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False) self.apply_button.set_sensitive(False)
self.play() self.play()
# Record (Capture) action. # Record (Capture) action.
else: else:
# Create a new temporary file. # Create a new temporary file.
tmpfile = tempfile.NamedTemporaryFile(delete=False) tmpfile = tempfile.NamedTemporaryFile(delete=False)
tmpfile.close() tmpfile.close()
self.filename = tmpfile.name self.filename = tmpfile.name
# Capture the current image. # Capture the current image.
self.take_picture(self.filename) self.take_picture(self.filename)
# Set the record button to retry, and disable it until the capture # Set the record button to retry, and disable it until the capture
# finishes. # finishes.
self.record_button.set_label(_("Retry")) self.record_button.set_label(_("Retry"))
self.record_button.set_sensitive(False) self.record_button.set_sensitive(False)
def on_camera_apply_clicked(self, widget): def on_camera_apply_clicked(self, widget):
"""When the camera Apply button is clicked, crop the current photo and """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 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.center_crop(self.filename)
self.emit("apply", self.filename) self.emit("apply", self.filename)
self.hide() self.hide()
def on_camera_cancel_clicked(self, widget): def on_camera_cancel_clicked(self, widget):
"""When the Cancel button is clicked, just hide the dialog.""" """When the Cancel button is clicked, just hide the dialog."""
self.hide() self.hide()
@ -285,54 +297,56 @@ class CameraMugshotDialog(CameraDialog):
os.remove(self.filename) os.remove(self.filename)
# Clean up the camera before exiting # Clean up the camera before exiting
self.camerabin.set_state(Gst.State.NULL) self.camerabin.set_state(Gst.State.NULL)
def on_camera_mugshot_dialog_hide(self, widget, data=None): def on_camera_mugshot_dialog_hide(self, widget, data=None):
"""When the dialog is hidden, pause the camera recording.""" """When the dialog is hidden, pause the camera recording."""
self.pause() self.pause()
def on_camera_mugshot_dialog_show(self, widget, data=None): 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.""" the apply button, and start the camera."""
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD) self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False) self.apply_button.set_sensitive(False)
self.show_all() self.show_all()
self.play() self.play()
def center_crop(self, filename): def center_crop(self, filename):
"""Crop the specified file to square dimensions.""" """Crop the specified file to square dimensions."""
# Load the image into a Pixbuf. # Load the image into a Pixbuf.
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
# Get the image dimensions. # Get the image dimensions.
height = pixbuf.get_height() height = pixbuf.get_height()
width = pixbuf.get_width() width = pixbuf.get_width()
start_x = 0 start_x = 0
start_y = 0 start_y = 0
# Calculate a balanced center. # Calculate a balanced center.
if width > height: if width > height:
start_x = (width-height)/2 start_x = (width - height) / 2
width = height width = height
else: else:
start_y = (height-width)/2 start_y = (height - width) / 2
height = width height = width
# Create a new cropped pixbuf. # Create a new cropped pixbuf.
new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height) new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height)
# Overwrite the temporary file with our new cropped image. # Overwrite the temporary file with our new cropped image.
new_pixbuf.savev(filename, "png", [], []) new_pixbuf.savev(filename, "png", [], [])
def on_camera_mugshot_dialog_delete_event(self, widget, data=None): def on_camera_mugshot_dialog_delete_event(self, widget, data=None):
"""Override the dialog delete event to just hide the window.""" """Override the dialog delete event to just hide the window."""
self.hide() self.hide()
return True return True
# Signals used by CameraMugshotDialog: # Signals used by CameraMugshotDialog:
# image-captured: emitted when the camera is done capturing an image. # 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. # apply: emitted when the apply button has been pressed and there is a
__gsignals__ = {'image-captured' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, # new file saved for use.
(GObject.TYPE_PYOBJECT,)), __gsignals__ = {'image-captured': (GObject.SIGNAL_RUN_LAST,
'apply' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)) 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 -*- # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE ### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com> # Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it # 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 # under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation. # by the Free Software Foundation.
# #
# This program is distributed in the hope that it will be useful, but # This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of # WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details. # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE ### END LICENSE
@ -28,7 +28,7 @@ import dbus
import tempfile import tempfile
from gi.repository import Gtk, Gdk, GdkPixbuf # pylint: disable=E0611 from gi.repository import Gtk, GdkPixbuf # pylint: disable=E0611
import logging import logging
logger = logging.getLogger('mugshot') 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') pidgin_prefs = os.path.join(home, '.purple', 'prefs.xml')
faces_dir = '/usr/share/pixmaps/faces/' faces_dir = '/usr/share/pixmaps/faces/'
def which(command): def which(command):
'''Use the system command which to get the absolute path for the given '''Use the system command which to get the absolute path for the given
command.''' command.'''
command = subprocess.Popen(['which', command], \ command = subprocess.Popen(['which', command],
stdout=subprocess.PIPE).stdout.read().strip() stdout=subprocess.PIPE).stdout.read().strip()
if command == '': if command == '':
logger.debug('Command "%s" could not be found.' % command) logger.debug('Command "%s" could not be found.' % command)
return None return None
return command return command
def has_running_process(name): def has_running_process(name):
"""Check for a running process, return True if any listings are found.""" """Check for a running process, return True if any listings are found."""
command = 'ps -ef | grep " %s" | grep -v "grep" | wc -l' % name 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() shell=True).stdout.read().strip()
return int(n) > 0 return int(n) > 0
def has_gstreamer_camerabin_support(): def has_gstreamer_camerabin_support():
"""Return True if gstreamer1.0 camerabin element is available.""" """Return True if gstreamer1.0 camerabin element is available."""
process = subprocess.Popen(["gst-inspect-1.0", "camerabin"], process = subprocess.Popen(["gst-inspect-1.0", "camerabin"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
process.communicate() process.communicate()
has_support = process.returncode == 0 has_support = process.returncode == 0
if not has_support: 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 return has_support
def has_gstreamer_camerasrc_support(): def has_gstreamer_camerasrc_support():
"""Return True if gstreamer1.0 v4l2src element is available.""" """Return True if gstreamer1.0 v4l2src element is available."""
process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"], process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
process.communicate() process.communicate()
has_support = process.returncode == 0 has_support = process.returncode == 0
if not has_support: 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 return has_support
def get_camera_installed(): def get_camera_installed():
"""Return True if /dev/video0 exists.""" """Return True if /dev/video0 exists."""
if not os.path.exists('/dev/video0'): if not os.path.exists('/dev/video0'):
logger.debug('Camera not detected at /dev/video0') logger.debug('Camera not detected at /dev/video0')
return False return False
return True return True
def get_has_camera_support(): def get_has_camera_support():
"""Return True if cameras are fully supported by this application.""" """Return True if cameras are fully supported by this application."""
if not get_camera_installed(): if not get_camera_installed():
@ -102,10 +114,12 @@ def get_has_camera_support():
return False return False
return True return True
def detach_cb(menu, widget): def detach_cb(menu, widget):
'''Detach a widget from its attached widget.''' '''Detach a widget from its attached widget.'''
menu.detach() menu.detach()
def get_entry_value(entry_widget): def get_entry_value(entry_widget):
"""Get the value from one of the Mugshot entries.""" """Get the value from one of the Mugshot entries."""
# Get the text from an entry, changing none to '' # Get the text from an entry, changing none to ''
@ -113,10 +127,11 @@ def get_entry_value(entry_widget):
if value.lower() == 'none': if value.lower() == 'none':
value = '' value = ''
return value return value
def get_confirmation_dialog(parent, primary_message, secondary_message,
def get_confirmation_dialog(parent, primary_message, secondary_message,
icon_name=None): 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.""" secondary messages, as well as a custom icon if requested."""
dialog = Gtk.MessageDialog(parent, flags=0, type=Gtk.MessageType.QUESTION, dialog = Gtk.MessageDialog(parent, flags=0, type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO, buttons=Gtk.ButtonsType.YES_NO,
@ -129,7 +144,8 @@ def get_confirmation_dialog(parent, primary_message, secondary_message,
response = dialog.run() response = dialog.run()
dialog.destroy() dialog.destroy()
return response == Gtk.ResponseType.YES return response == Gtk.ResponseType.YES
def menu_position(self, menu, data=None, something_else=None): def menu_position(self, menu, data=None, something_else=None):
'''Position a menu at the bottom of its attached widget''' '''Position a menu at the bottom of its attached widget'''
widget = menu.get_attach_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 y = window_pos[1] + allocation.y + allocation.height
return (x, y, True) return (x, y, True)
# See mugshot_lib.Window.py for more details about how this class works # See mugshot_lib.Window.py for more details about how this class works
class MugshotWindow(Window): class MugshotWindow(Window):
"""Mugshot GtkWindow"""
__gtype_name__ = "MugshotWindow" __gtype_name__ = "MugshotWindow"
def finish_initializing(self, builder): # pylint: disable=E1002 def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window""" """Set up the main window"""
super(MugshotWindow, self).finish_initializing(builder) super(MugshotWindow, self).finish_initializing(builder)
self.set_wmclass("Mugshot", "Mugshot") self.set_wmclass("Mugshot", "Mugshot")
self.CameraDialog = CameraMugshotDialog self.CameraDialog = CameraMugshotDialog
# User Image widgets # User Image widgets
@ -159,9 +177,9 @@ class MugshotWindow(Window):
self.image_menu.attach_to_widget(self.image_button, detach_cb) self.image_menu.attach_to_widget(self.image_button, detach_cb)
self.image_from_camera = builder.get_object('image_from_camera') self.image_from_camera = builder.get_object('image_from_camera')
image_from_browse = builder.get_object('image_from_browse') image_from_browse = builder.get_object('image_from_browse')
image_from_browse.set_visible( os.path.exists(faces_dir) and \ image_from_browse.set_visible(os.path.exists(faces_dir) and
len(os.listdir(faces_dir)) > 0 ) len(os.listdir(faces_dir)) > 0)
# Entry widgets (chfn) # Entry widgets (chfn)
self.first_name_entry = builder.get_object('first_name') self.first_name_entry = builder.get_object('first_name')
self.last_name_entry = builder.get_object('last_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.home_phone_entry = builder.get_object('home_phone')
self.email_entry = builder.get_object('email') self.email_entry = builder.get_object('email')
self.fax_entry = builder.get_object('fax') self.fax_entry = builder.get_object('fax')
# Stock photo browser # Stock photo browser
self.stock_browser = builder.get_object('stock_browser') self.stock_browser = builder.get_object('stock_browser')
self.iconview = builder.get_object('stock_iconview') self.iconview = builder.get_object('stock_iconview')
# File Chooser Dialog # File Chooser Dialog
self.chooser = builder.get_object('filechooserdialog') self.chooser = builder.get_object('filechooserdialog')
self.crop_center = builder.get_object('crop_center') self.crop_center = builder.get_object('crop_center')
@ -186,12 +204,12 @@ class MugshotWindow(Window):
image_filter.set_name('Images') image_filter.set_name('Images')
image_filter.add_mime_type('image/*') image_filter.add_mime_type('image/*')
self.chooser.add_filter(image_filter) self.chooser.add_filter(image_filter)
self.tmpfile = None self.tmpfile = None
# Populate all of the widgets. # Populate all of the widgets.
self.init_user_details() self.init_user_details()
def init_user_details(self): def init_user_details(self):
"""Initialize the user details entries and variables.""" """Initialize the user details entries and variables."""
# Check for .face and set profile image. # Check for .face and set profile image.
@ -202,7 +220,7 @@ class MugshotWindow(Window):
else: else:
self.set_user_image(None) self.set_user_image(None)
self.updated_image = None self.updated_image = None
# Search /etc/passwd for the current user's details. # Search /etc/passwd for the current user's details.
logger.debug('Getting user details from /etc/passwd') logger.debug('Getting user details from /etc/passwd')
for line in open('/etc/passwd', 'r'): for line in open('/etc/passwd', 'r'):
@ -211,7 +229,7 @@ class MugshotWindow(Window):
details = line.split(':')[4] details = line.split(':')[4]
name, office, office_phone, home_phone = details.split(',', 3) name, office, office_phone, home_phone = details.split(',', 3)
break break
# Expand the user's fullname into first, last, and initials. # Expand the user's fullname into first, last, and initials.
try: try:
first_name, last_name = name.split(' ', 1) first_name, last_name = name.split(' ', 1)
@ -220,25 +238,27 @@ class MugshotWindow(Window):
first_name = name first_name = name
last_name = '' last_name = ''
initials = first_name[0] initials = first_name[0]
# If the variables are defined as 'none', use blank for cleanliness. # If the variables are defined as 'none', use blank for cleanliness.
if home_phone == 'none': home_phone = '' if home_phone == 'none':
if office_phone == 'none': office_phone = '' home_phone = ''
if office_phone == 'none':
office_phone = ''
# Get dconf settings # Get dconf settings
logger.debug('Getting initials, email, and fax from dconf') logger.debug('Getting initials, email, and fax from dconf')
if self.settings['initials'] != '': if self.settings['initials'] != '':
initials = self.settings['initials'] initials = self.settings['initials']
email = self.settings['email'] email = self.settings['email']
fax = self.settings['fax'] fax = self.settings['fax']
# Set the class variables # Set the class variables
self.first_name = first_name self.first_name = first_name
self.last_name = last_name self.last_name = last_name
self.initials = initials self.initials = initials
self.home_phone = home_phone self.home_phone = home_phone
self.office_phone = office_phone self.office_phone = office_phone
# Populate the GtkEntries. # Populate the GtkEntries.
logger.debug('Populating entries') logger.debug('Populating entries')
self.first_name_entry.set_text(self.first_name) 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.home_phone_entry.set_text(self.home_phone)
self.email_entry.set_text(email) self.email_entry.set_text(email)
self.fax_entry.set_text(fax) self.fax_entry.set_text(fax)
# = Mugshot Window ======================================================= # # = Mugshot Window ======================================================= #
def set_user_image(self, filename=None): def set_user_image(self, filename=None):
"""Scale and set the user profile image.""" """Scale and set the user profile image."""
@ -264,83 +284,84 @@ class MugshotWindow(Window):
"""Allow only numbers and + in phone entry fields.""" """Allow only numbers and + in phone entry fields."""
text = entry.get_text().strip() text = entry.get_text().strip()
entry.set_text(''.join([i for i in text if i in '+0123456789'])) entry.set_text(''.join([i for i in text if i in '+0123456789']))
def on_apply_button_clicked(self, widget): 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.""" changes."""
logger.debug('Applying changes...') logger.debug('Applying changes...')
if self.get_chfn_details_updated(): if self.get_chfn_details_updated():
returns = self.save_chfn_details() self.save_chfn_details()
if self.get_libreoffice_details_updated(): if self.get_libreoffice_details_updated():
self.set_libreoffice_data() self.set_libreoffice_data()
if self.updated_image: if self.updated_image:
self.save_image() self.save_image()
self.save_gsettings() self.save_gsettings()
self.destroy() self.destroy()
def save_gsettings(self): def save_gsettings(self):
"""Save details to dconf (the ones not tracked by /etc/passwd)""" """Save details to dconf (the ones not tracked by /etc/passwd)"""
logger.debug('Saving details to dconf: /apps/mugshot') logger.debug('Saving details to dconf: /apps/mugshot')
self.settings.set_string('initials', self.settings.set_string('initials',
get_entry_value(self.initials_entry)) get_entry_value(self.initials_entry))
self.settings.set_string('email', get_entry_value(self.email_entry)) self.settings.set_string('email', get_entry_value(self.email_entry))
self.settings.set_string('fax', get_entry_value(self.fax_entry)) self.settings.set_string('fax', get_entry_value(self.fax_entry))
def entry_focus_next(self, widget): def entry_focus_next(self, widget):
"""Focus the next available entry when pressing Enter.""" """Focus the next available entry when pressing Enter."""
logger.debug('Entry activated, focusing next widget.') logger.debug('Entry activated, focusing next widget.')
vbox = widget.get_parent().get_parent().get_parent().get_parent() vbox = widget.get_parent().get_parent().get_parent().get_parent()
vbox.child_focus(Gtk.DirectionType.TAB_FORWARD) vbox.child_focus(Gtk.DirectionType.TAB_FORWARD)
def on_cancel_button_clicked(self, widget): def on_cancel_button_clicked(self, widget):
"""When the window cancel button is clicked, close the program.""" """When the window cancel button is clicked, close the program."""
logger.debug('Cancel clicked, goodbye.') logger.debug('Cancel clicked, goodbye.')
self.destroy() self.destroy()
# = Image Button and Menu ================================================ # # = Image Button and Menu ================================================ #
def on_image_button_clicked(self, widget): def on_image_button_clicked(self, widget):
"""When the menu button is clicked, display the photo menu.""" """When the menu button is clicked, display the photo menu."""
if widget.get_active(): if widget.get_active():
logger.debug('Show photo menu') logger.debug('Show photo menu')
self.image_from_camera.set_visible(get_has_camera_support()) self.image_from_camera.set_visible(get_has_camera_support())
self.image_menu.popup(None, None, menu_position, self.image_menu.popup(None, None, menu_position,
self.image_menu, 3, self.image_menu, 3,
Gtk.get_current_event_time()) Gtk.get_current_event_time())
def on_image_menu_hide(self, widget): def on_image_menu_hide(self, widget):
"""Untoggle the image button when the menu is hidden.""" """Untoggle the image button when the menu is hidden."""
self.image_button.set_active(False) self.image_button.set_active(False)
def on_camera_dialog_apply(self, widget, data=None): def on_camera_dialog_apply(self, widget, data=None):
"""Commit changes when apply is clicked."""
self.updated_image = data self.updated_image = data
self.set_user_image(data) self.set_user_image(data)
def save_image(self): def save_image(self):
"""Copy the updated image filename to ~/.face""" """Copy the updated image filename to ~/.face"""
# Check if the image has been updated. # Check if the image has been updated.
if not self.updated_image: if not self.updated_image:
logger.debug('Photo not updated, not saving changes.') logger.debug('Photo not updated, not saving changes.')
return False return False
face = os.path.expanduser('~/.face') face = os.path.expanduser('~/.face')
# If the .face file already exists, remove it first. # If the .face file already exists, remove it first.
logger.debug('Photo updated, saving changes.') logger.debug('Photo updated, saving changes.')
if os.path.isfile(face): if os.path.isfile(face):
os.remove(face) os.remove(face)
# Copy the new file to ~/.face # Copy the new file to ~/.face
shutil.copyfile(self.updated_image, face) shutil.copyfile(self.updated_image, face)
self.set_pidgin_buddyicon(face) self.set_pidgin_buddyicon(face)
self.updated_image = None self.updated_image = None
return True return True
def set_pidgin_buddyicon(self, filename=None): def set_pidgin_buddyicon(self, filename=None):
"""Sets the pidgin buddyicon to filename (usually ~/.face). """Sets the pidgin buddyicon to filename (usually ~/.face).
If pidgin is running, use the dbus interface, otherwise directly modify If pidgin is running, use the dbus interface, otherwise directly modify
the XML file.""" the XML file."""
if not os.path.exists(pidgin_prefs): if not os.path.exists(pidgin_prefs):
@ -358,19 +379,19 @@ class MugshotWindow(Window):
self.set_pidgin_buddyicon_xml(filename) self.set_pidgin_buddyicon_xml(filename)
else: else:
logger.debug('Reject: Not updating pidgin buddy icon') logger.debug('Reject: Not updating pidgin buddy icon')
def set_pidgin_buddyicon_dbus(self, filename=None): def set_pidgin_buddyicon_dbus(self, filename=None):
"""Set the pidgin buddy icon via dbus.""" """Set the pidgin buddy icon via dbus."""
logger.debug('Updating pidgin buddy icon via dbus') logger.debug('Updating pidgin buddy icon via dbus')
bus = dbus.SessionBus() bus = dbus.SessionBus()
obj = bus.get_object("im.pidgin.purple.PurpleService", obj = bus.get_object("im.pidgin.purple.PurpleService",
"/im/pidgin/purple/PurpleObject") "/im/pidgin/purple/PurpleObject")
purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
# To make the change instantly visible, set the icon to none first. # To make the change instantly visible, set the icon to none first.
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', '') purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', '')
if filename: if filename:
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', filename) purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', filename)
def set_pidgin_buddyicon_xml(self, filename=None): def set_pidgin_buddyicon_xml(self, filename=None):
"""Set the buddyicon used by pidgin to filename (via the xml file).""" """Set the buddyicon used by pidgin to filename (via the xml file)."""
# This is hacky, but a working implementation for now... # This is hacky, but a working implementation for now...
@ -380,9 +401,9 @@ class MugshotWindow(Window):
if os.path.isfile(prefs_file): if os.path.isfile(prefs_file):
for line in open(prefs_file): for line in open(prefs_file):
if '<pref name=\'buddyicon\'' in line: if '<pref name=\'buddyicon\'' in line:
new = line.split('value=')[0] new = line.split('value=')[0]
if filename: if filename:
new = new + 'value=\'%s\'/>\n' % filename new = new + 'value=\'%s\'/>\n' % filename
else: else:
new = new + 'value=\'\'/>\n' new = new + 'value=\'\'/>\n'
tmp_buffer.append(new) tmp_buffer.append(new)
@ -392,7 +413,7 @@ class MugshotWindow(Window):
for line in tmp_buffer: for line in tmp_buffer:
write_prefs.write(line) write_prefs.write(line)
write_prefs.close() write_prefs.close()
# = chfn functions ============================================ # # = chfn functions ============================================ #
def get_chfn_details_updated(self): def get_chfn_details_updated(self):
"""Return True if chfn-related details have been modified.""" """Return True if chfn-related details have been modified."""
@ -405,25 +426,25 @@ class MugshotWindow(Window):
return True return True
logger.debug('chfn details have NOT been modified.') logger.debug('chfn details have NOT been modified.')
return False return False
def save_chfn_details(self): def save_chfn_details(self):
"""Commit changes to chfn-related details. For full name, changes must """Commit changes to chfn-related details. For full name, changes must
be performed as root. Other changes are done with the user password. 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 Return exit codes for 1) full name changes and 2) home/work phone
changes. changes.
e.g. [0, 0] (both passed)""" e.g. [0, 0] (both passed)"""
return_codes = [] return_codes = []
# Get the user's password # Get the user's password
password = self.get_password() password = self.get_password()
if not password: if not password:
return return_codes return return_codes
username = os.getenv('USER') username = os.getenv('USER')
chfn = which('chfn') chfn = which('chfn')
# Get each of the updated values. # Get each of the updated values.
first_name = get_entry_value(self.first_name_entry) first_name = get_entry_value(self.first_name_entry)
last_name = get_entry_value(self.last_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) home_phone = get_entry_value(self.home_phone_entry)
if home_phone == '': if home_phone == '':
home_phone = 'none' home_phone = 'none'
# Full name can only be modified by root. Try using sudo to modify. # Full name can only be modified by root. Try using sudo to modify.
logger.debug('Attempting to set fullname with sudo chfn') logger.debug('Attempting to set fullname with sudo chfn')
child = pexpect.spawn('sudo %s %s' % (chfn, username)) child = pexpect.spawn('sudo %s %s' % (chfn, username))
@ -449,9 +470,9 @@ class MugshotWindow(Window):
child.sendline('') child.sendline('')
except pexpect.TIMEOUT: except pexpect.TIMEOUT:
# Password was incorrect, or sudo rights not granted # 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.') 'rights not granted.')
pass pass
child.close() child.close()
if child.exitstatus == 0: if child.exitstatus == 0:
self.first_name = first_name self.first_name = first_name
@ -479,7 +500,7 @@ class MugshotWindow(Window):
self.home_phone = home_phone self.home_phone = home_phone
return_codes.append(child.exitstatus) return_codes.append(child.exitstatus)
return return_codes return return_codes
# = LibreOffice ========================================================== # # = LibreOffice ========================================================== #
def get_libreoffice_details_updated(self): def get_libreoffice_details_updated(self):
"""Return True if LibreOffice settings need to be updated.""" """Return True if LibreOffice settings need to be updated."""
@ -506,14 +527,14 @@ class MugshotWindow(Window):
return True return True
logger.debug('LibreOffice details do not need to be updated.') logger.debug('LibreOffice details do not need to be updated.')
return False return False
def get_libreoffice_data(self): def get_libreoffice_data(self):
"""Get each of the preferences from the LibreOffice registymodifications """Get each of the preferences from the LibreOffice registymodifications
preferences file. preferences file.
Return a dict with the details.""" Return a dict with the details."""
prefs_file = libreoffice_prefs prefs_file = libreoffice_prefs
data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '', data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '',
'home_phone': '', 'office_phone': '', 'fax': ''} 'home_phone': '', 'office_phone': '', 'fax': ''}
if os.path.isfile(prefs_file): if os.path.isfile(prefs_file):
logger.debug('Getting settings from %s' % prefs_file) logger.debug('Getting settings from %s' % prefs_file)
@ -545,7 +566,7 @@ class MugshotWindow(Window):
else: else:
pass pass
return data return data
def set_libreoffice_data(self): def set_libreoffice_data(self):
"""Update the LibreOffice registymodifications preferences file.""" """Update the LibreOffice registymodifications preferences file."""
prefs_file = libreoffice_prefs prefs_file = libreoffice_prefs
@ -622,40 +643,61 @@ class MugshotWindow(Window):
open_prefs = open(prefs_file, 'w') open_prefs = open(prefs_file, 'w')
for line in tmp_buffer: for line in tmp_buffer:
open_prefs.write(line) open_prefs.write(line)
if not first_name_updated: 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) open_prefs.write(string)
if not last_name_updated: 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) open_prefs.write(string)
if not initials_updated: 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) open_prefs.write(string)
if not email_updated: 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) open_prefs.write(string)
if not home_phone_updated: 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) open_prefs.write(string)
if not office_phone_updated: 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) open_prefs.write(string)
if not fax_updated: 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(string)
open_prefs.write('</oor:items>') open_prefs.write('</oor:items>')
open_prefs.close() open_prefs.close()
else: else:
logger.debug('Reject: Not updating.') logger.debug('Reject: Not updating.')
# = Stock Browser ======================================================== # # = Stock Browser ======================================================== #
def on_image_from_stock_activate(self, widget): 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.""" display the stock photo browser."""
self.load_stock_browser() self.load_stock_browser()
self.stock_browser.show_all() self.stock_browser.show_all()
def load_stock_browser(self): def load_stock_browser(self):
"""Load the stock photo browser.""" """Load the stock photo browser."""
# Check if the photos have already been loaded. # Check if the photos have already been loaded.
@ -663,7 +705,7 @@ class MugshotWindow(Window):
if len(model) != 0: if len(model) != 0:
logger.debug("Stock browser already loaded.") logger.debug("Stock browser already loaded.")
return return
# If they have not, load each photo from /usr/share/pixmaps/faces. # If they have not, load each photo from /usr/share/pixmaps/faces.
logger.debug("Loading stock browser photos.") logger.debug("Loading stock browser photos.")
for filename in os.listdir('/usr/share/pixmaps/faces'): for filename in os.listdir('/usr/share/pixmaps/faces'):
@ -672,24 +714,24 @@ class MugshotWindow(Window):
pixbuf = GdkPixbuf.Pixbuf.new_from_file(full_path) pixbuf = GdkPixbuf.Pixbuf.new_from_file(full_path)
scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER) scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER)
model.append([full_path, scaled]) model.append([full_path, scaled])
def on_stock_iconview_selection_changed(self, widget): def on_stock_iconview_selection_changed(self, widget):
"""Enable stock submission only when an item is selected.""" """Enable stock submission only when an item is selected."""
selected_items = self.iconview.get_selected_items() selected_items = self.iconview.get_selected_items()
self.builder.get_object('stock_ok').set_sensitive( self.builder.get_object('stock_ok').set_sensitive(
len(selected_items) > 0) len(selected_items) > 0)
def on_stock_browser_delete_event(self, widget, event): def on_stock_browser_delete_event(self, widget, event):
"""Hide the stock browser instead of deleting it.""" """Hide the stock browser instead of deleting it."""
widget.hide() widget.hide()
return True return True
def on_stock_cancel_clicked(self, widget): def on_stock_cancel_clicked(self, widget):
"""Hide the stock browser when Cancel is clicked.""" """Hide the stock browser when Cancel is clicked."""
self.stock_browser.hide() self.stock_browser.hide()
def on_stock_ok_clicked(self, widget): 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 photo and set it to the user profile image."""
selected_items = self.iconview.get_selected_items() selected_items = self.iconview.get_selected_items()
if len(selected_items) != 0: if len(selected_items) != 0:
@ -697,16 +739,16 @@ class MugshotWindow(Window):
path = int(selected_items[0].to_string()) path = int(selected_items[0].to_string())
filename = self.iconview.get_model()[path][0] filename = self.iconview.get_model()[path][0]
logger.debug("Selected %s" % filename) logger.debug("Selected %s" % filename)
# Update variables and widgets, then hide. # Update variables and widgets, then hide.
self.set_user_image(filename) self.set_user_image(filename)
self.updated_image = filename self.updated_image = filename
self.stock_browser.hide() self.stock_browser.hide()
def on_stock_iconview_item_activated(self, widget, path): def on_stock_iconview_item_activated(self, widget, path):
"""Allow selecting a stock photo with Enter.""" """Allow selecting a stock photo with Enter."""
self.on_stock_ok_clicked(widget) self.on_stock_ok_clicked(widget)
# = Image Browser ======================================================== # # = Image Browser ======================================================== #
def on_image_from_browse_activate(self, widget): def on_image_from_browse_activate(self, widget):
"""Browse for a user profile image.""" """Browse for a user profile image."""
@ -719,12 +761,14 @@ class MugshotWindow(Window):
self.tmpfile = tempfile.NamedTemporaryFile(delete=False) self.tmpfile = tempfile.NamedTemporaryFile(delete=False)
self.tmpfile.close() self.tmpfile.close()
self.updated_image = self.tmpfile.name 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) logger.debug("Selected %s" % self.updated_image)
self.set_user_image(self.updated_image) self.set_user_image(self.updated_image)
self.chooser.hide() self.chooser.hide()
def on_filechooserdialog_update_preview(self, widget): def on_filechooserdialog_update_preview(self, widget):
"""Update the preview image used in the file chooser."""
filename = widget.get_filename() filename = widget.get_filename()
if not filename: if not filename:
self.file_chooser_preview.set_from_icon_name('folder', 128) 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) self.file_chooser_preview.set_from_icon_name('folder', 128)
return return
filechooser_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) filechooser_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
# Get the image dimensions. # Get the image dimensions.
height = filechooser_pixbuf.get_height() height = filechooser_pixbuf.get_height()
width = filechooser_pixbuf.get_width() width = filechooser_pixbuf.get_width()
start_x = 0 start_x = 0
start_y = 0 start_y = 0
if self.crop_center.get_active(): if self.crop_center.get_active():
# Calculate a balanced center. # Calculate a balanced center.
if width > height: if width > height:
start_x = (width-height)/2 start_x = (width - height) / 2
width = height width = height
else: else:
start_y = (height-width)/2 start_y = (height - width) / 2
height = width height = width
elif self.crop_left.get_active(): elif self.crop_left.get_active():
start_x = 0 start_x = 0
if width > height: if width > height:
width = height width = height
else: else:
start_y = (height-width)/2 start_y = (height - width) / 2
height = width height = width
elif self.crop_right.get_active(): elif self.crop_right.get_active():
if width > height: if width > height:
start_x = width-height start_x = width - height
width = height width = height
else: else:
start_y = (height-width)/2 start_y = (height - width) / 2
height = width height = width
# Create a new cropped pixbuf. # Create a new cropped pixbuf.
self.filechooser_preview_pixbuf = filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height) 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)
scaled = self.filechooser_preview_pixbuf.scale_simple(128, 128,
GdkPixbuf.InterpType.HYPER)
self.file_chooser_preview.set_from_pixbuf(scaled) self.file_chooser_preview.set_from_pixbuf(scaled)
def on_crop_changed(self, widget, data=None): def on_crop_changed(self, widget, data=None):
"""Update the preview image when crop style is modified."""
if widget.get_active(): if widget.get_active():
self.on_filechooserdialog_update_preview(self.chooser) self.on_filechooserdialog_update_preview(self.chooser)
# = Password Entry ======================================================= # # = Password Entry ======================================================= #
def get_password(self): def get_password(self):
"""Display a password dialog for authenticating to sudo and chfn.""" """Display a password dialog for authenticating to sudo and chfn."""
@ -789,9 +836,8 @@ class MugshotWindow(Window):
return pw return pw
logger.debug("Cancelled") logger.debug("Cancelled")
return None return None
def on_password_entry_changed(self, widget): def on_password_entry_changed(self, widget):
"""Enable password submission only when password is not blank.""" """Enable password submission only when password is not blank."""
self.builder.get_object('password_ok').set_sensitive( self.builder.get_object('password_ok').set_sensitive(
len(widget.get_text()) > 0) len(widget.get_text()) > 0)

View File

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

View File

@ -1,34 +1,36 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE ### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com> # Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it # 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 # under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation. # by the Free Software Foundation.
# #
# This program is distributed in the hope that it will be useful, but # This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of # WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details. # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE ### END LICENSE
### DO NOT EDIT THIS FILE ### ### DO NOT EDIT THIS FILE ###
from gi.repository import Gtk # pylint: disable=E0611 from gi.repository import Gtk # pylint: disable=E0611
import logging import logging
logger = logging.getLogger('mugshot_lib') logger = logging.getLogger('mugshot_lib')
from . helpers import get_builder, show_uri, get_help_uri from . helpers import get_builder, show_uri, get_help_uri
class CameraDialog(Gtk.Dialog): class CameraDialog(Gtk.Dialog):
"""Camera Dialog"""
__gtype_name__ = "CameraDialog" __gtype_name__ = "CameraDialog"
def __new__(cls): 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. constructing a new instance of this class.
Returns a fully instantiated PreferencesDialog object. Returns a fully instantiated PreferencesDialog object.
""" """
builder = get_builder('CameraMugshotDialog') builder = get_builder('CameraMugshotDialog')
@ -43,7 +45,7 @@ class CameraDialog(Gtk.Dialog):
and creating a PreferencesDialog object with it in order to and creating a PreferencesDialog object with it in order to
finish initializing the start of the new PerferencesMugshotDialog finish initializing the start of the new PerferencesMugshotDialog
instance. instance.
Put your initialization code in here and leave __init__ undefined. 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 # code for other initialization actions should be added here
def on_btn_close_clicked(self, widget, data=None): def on_btn_close_clicked(self, widget, data=None):
"""Destroy the dialog when closed."""
self.destroy() self.destroy()
def on_btn_help_clicked(self, widget, data=None): 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')) 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 -*- # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE ### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com> # Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it # 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 # under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation. # by the Free Software Foundation.
# #
# This program is distributed in the hope that it will be useful, but # This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of # WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details. # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE ### END LICENSE
### DO NOT EDIT THIS FILE ### ### 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 import logging
logger = logging.getLogger('mugshot_lib') logger = logging.getLogger('mugshot_lib')
@ -24,12 +24,13 @@ import os
from . helpers import get_builder, show_uri, get_help_uri 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): class Window(Gtk.Window):
"""This class is meant to be subclassed by MugshotWindow. It provides
common functions and some boilerplate."""
__gtype_name__ = "Window" __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: # methods are called in this order:
# __new__(cls) # __new__(cls)
# __init__(self) # __init__(self)
@ -38,11 +39,11 @@ class Window(Gtk.Window):
# #
# For this reason, it's recommended you leave __init__ empty and put # For this reason, it's recommended you leave __init__ empty and put
# your initialization code in finish_initializing # your initialization code in finish_initializing
def __new__(cls): 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. constructing a new instance of this class.
Returns a fully instantiated BaseMugshotWindow object. Returns a fully instantiated BaseMugshotWindow object.
""" """
builder = get_builder('MugshotWindow') builder = get_builder('MugshotWindow')
@ -60,15 +61,16 @@ class Window(Gtk.Window):
# Get a reference to the builder and set up the signals. # Get a reference to the builder and set up the signals.
self.builder = builder self.builder = builder
self.ui = builder.get_ui(self, True) self.ui = builder.get_ui(self, True)
self.CameraDialog = None # class self.CameraDialog = None # class
self.camera_dialog = None # instance self.camera_dialog = None # instance
self.settings = Gio.Settings("apps.mugshot") self.settings = Gio.Settings("apps.mugshot")
self.settings.connect('changed', self.on_preferences_changed) self.settings.connect('changed', self.on_preferences_changed)
def on_help_activate(self, widget, data=None): def on_help_activate(self, widget, data=None):
"""Show the Help documentation when Help is clicked."""
show_uri(self, "ghelp:%s" % get_help_uri()) show_uri(self, "ghelp:%s" % get_help_uri())
def on_menu_camera_activate(self, widget, data=None): def on_menu_camera_activate(self, widget, data=None):
"""Display the camera window for mugshot.""" """Display the camera window for mugshot."""
if self.camera_dialog is not None: if self.camera_dialog is not None:
@ -76,8 +78,7 @@ class Window(Gtk.Window):
self.camera_dialog.show() self.camera_dialog.show()
elif self.CameraDialog is not None: elif self.CameraDialog is not None:
logger.debug('create new camera_dialog') logger.debug('create new camera_dialog')
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102 self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
#self.camera_dialog.connect('destroy', self.on_camera_dialog_destroyed)
self.camera_dialog.connect('apply', self.on_camera_dialog_apply) self.camera_dialog.connect('apply', self.on_camera_dialog_apply)
self.camera_dialog.show() self.camera_dialog.show()
@ -89,5 +90,6 @@ class Window(Gtk.Window):
Gtk.main_quit() Gtk.main_quit()
def on_preferences_changed(self, settings, key, data=None): 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 -*- # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE ### BEGIN LICENSE
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com> # Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
# This program is free software: you can redistribute it and/or modify it # 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 # under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation. # by the Free Software Foundation.
# #
# This program is distributed in the hope that it will be useful, but # This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of # WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details. # 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/>. # with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE ### END LICENSE
@ -23,12 +23,11 @@ import os
from . mugshotconfig import get_data_file from . mugshotconfig import get_data_file
from . Builder import Builder from . Builder import Builder
from locale import gettext as _
def get_builder(builder_file_name): 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 file
:param builder_file_name: The name of the builder file, without extension. :param builder_file_name: The name of the builder file, without extension.
Assumed to be in the 'ui' directory under the data path. Assumed to be in the 'ui' directory under the data path.
""" """
@ -43,25 +42,31 @@ def get_builder(builder_file_name):
return builder return builder
# Owais Lone : To get quick access to icons and stuff.
def get_media_file(media_file_name): def get_media_file(media_file_name):
"""Retrieve the filename for the specified file."""
media_filename = get_data_file('media', '%s' % (media_file_name,)) media_filename = get_data_file('media', '%s' % (media_file_name,))
if not os.path.exists(media_filename): if not os.path.exists(media_filename):
media_filename = None media_filename = None
return "file:///"+media_filename return "file:///" + media_filename
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
"""Handle NULL"""
def emit(self, record): def emit(self, record):
"""Do not emit anything."""
pass pass
def set_up_logging(opts): def set_up_logging(opts):
"""Set up the logging formatter."""
# add a handler to prevent basicConfig # add a handler to prevent basicConfig
root = logging.getLogger() root = logging.getLogger()
null_handler = NullHandler() null_handler = NullHandler()
root.addHandler(null_handler) 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 = logging.getLogger('mugshot')
logger_sh = logging.StreamHandler() logger_sh = logging.StreamHandler()
@ -80,7 +85,9 @@ def set_up_logging(opts):
if opts.verbose > 1: if opts.verbose > 1:
lib_logger.setLevel(logging.DEBUG) lib_logger.setLevel(logging.DEBUG)
def get_help_uri(page=None): def get_help_uri(page=None):
"""Get the URI to be used for Help."""
# help_uri from source tree - default language # help_uri from source tree - default language
here = os.path.dirname(__file__) here = os.path.dirname(__file__)
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C')) help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
@ -95,11 +102,14 @@ def get_help_uri(page=None):
return help_uri return help_uri
def show_uri(parent, link): 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() screen = parent.get_screen()
Gtk.show_uri(screen, link, Gtk.get_current_event_time()) Gtk.show_uri(screen, link, Gtk.get_current_event_time())
def alias(alternative_function_name): def alias(alternative_function_name):
'''see http://www.drdobbs.com/web-development/184406073#l9''' '''see http://www.drdobbs.com/web-development/184406073#l9'''
def decorator(function): def decorator(function):

View File

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