Un-quickly
This commit is contained in:
parent
fe99f93bbc
commit
b40faf2dca
6
.quickly
6
.quickly
|
@ -1,6 +0,0 @@
|
|||
project = mugshot
|
||||
version = 12.08.1
|
||||
template = ubuntu-application
|
||||
dependencies =
|
||||
lp_id = mugshot
|
||||
ppa = mugshot-ppa
|
|
@ -1,343 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot')
|
||||
|
||||
from gi.repository import Gtk, GdkX11, GObject, Gst, GstVideo, GdkPixbuf
|
||||
import cairo
|
||||
|
||||
import tempfile, os
|
||||
|
||||
from mugshot_lib.CameraDialog import CameraDialog
|
||||
|
||||
class CameraMugshotDialog(CameraDialog):
|
||||
__gtype_name__ = "CameraMugshotDialog"
|
||||
|
||||
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)
|
||||
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:
|
||||
bus = self.camerabin.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.enable_sync_message_emission()
|
||||
bus.connect("message", self._on_message)
|
||||
bus.connect("sync-message::element", self._on_sync_message)
|
||||
self.realized = False
|
||||
self.draw_handler = self.video_window.connect('draw', self.on_draw)
|
||||
# If the camera fails to load, show an error on the screen.
|
||||
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)
|
||||
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."""
|
||||
# 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.paint()
|
||||
|
||||
# Set the font details.
|
||||
font_size = 20
|
||||
font_color = (255,255,255)
|
||||
font_name = "Sans"
|
||||
|
||||
# Translators: Please include the newline, required to fit the message.
|
||||
message = _("Sorry, but your camera\nfailed to initialize.")
|
||||
|
||||
# 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)
|
||||
ctx.move_to(10,(height-font_size)/2)
|
||||
ctx.show_text(message.split('\n')[0])
|
||||
ctx.move_to(10,(height-font_size)/2+font_size)
|
||||
ctx.show_text(message.split('\n')[1])
|
||||
|
||||
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."""
|
||||
# Get the height and width of the drawing area.
|
||||
alloc = widget.get_allocation()
|
||||
height = alloc.height
|
||||
width = alloc.width
|
||||
|
||||
# Set the font details.
|
||||
font_size = 20
|
||||
font_color = (255,255,255)
|
||||
font_name = "Sans"
|
||||
|
||||
# Translators: Please include the newline, required to fit the message.
|
||||
message = _("Please wait while your\ncamera is initialized.")
|
||||
|
||||
# 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)
|
||||
ctx.move_to(10,(height-font_size)/2)
|
||||
ctx.show_text(message.split('\n')[0])
|
||||
ctx.move_to(10,(height-font_size)/2+font_size)
|
||||
ctx.show_text(message.split('\n')[1])
|
||||
|
||||
# Redefine on_draw to blank the drawing area next time.
|
||||
def on_draw(self, widget, ctx):
|
||||
ctx.set_source_rgb(0,0,0)
|
||||
ctx.paint()
|
||||
# Redefine on_draw once more to do nothing else.
|
||||
def on_draw(self, widget, ctx):
|
||||
pass
|
||||
|
||||
def play(self):
|
||||
"""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"))
|
||||
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
|
||||
before play may cause errors on certain camera."""
|
||||
if self.camerabin:
|
||||
self.camerabin.set_state(Gst.State.PAUSED)
|
||||
|
||||
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.
|
||||
|
||||
Connect to the signal "image-captured" to be alerted when the picture
|
||||
is saved."""
|
||||
self.camerabin.set_property("location", filename)
|
||||
self.camerabin.emit("start-capture")
|
||||
|
||||
def stop(self):
|
||||
"""Stop the camera streaming and display the output."""
|
||||
self.camerabin.set_state(Gst.State.NULL)
|
||||
|
||||
def _on_message(self, bus, message):
|
||||
"""Internal signal handler for bus messages.
|
||||
May be useful to extend in a base class to handle messages
|
||||
produced from custom behaviors.
|
||||
|
||||
arguments -
|
||||
bus: the bus from which the message was sent, typically self.bux
|
||||
message: the message sent"""
|
||||
# Ignore if there is no message.
|
||||
if message is None:
|
||||
return
|
||||
|
||||
# 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)
|
||||
self.record_button.set_sensitive(True)
|
||||
self.pause()
|
||||
|
||||
# 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()
|
||||
logger.error("%s" % err, debug)
|
||||
|
||||
def _on_sync_message(self, bus, message):
|
||||
""" _on_sync_message - internal signal handler for bus messages.
|
||||
May be useful to extend in a base class to handle messages
|
||||
produced from custom behaviors.
|
||||
|
||||
arguments -
|
||||
bus: the bus from which the message was sent, typically self.bux
|
||||
message: the message sent
|
||||
|
||||
"""
|
||||
# Ignore empty messages.
|
||||
if message.get_structure() is None:
|
||||
return
|
||||
message_name = message.get_structure().get_name()
|
||||
# Embed the gstreamer element into our window.
|
||||
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())
|
||||
|
||||
def __on_video_window_realized(self, widget, data=None):
|
||||
"""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.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):
|
||||
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
|
||||
available. Then close the camera dialog."""
|
||||
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()
|
||||
|
||||
def on_camera_mugshot_dialog_destroy(self, widget, data=None):
|
||||
"""When the application exits, remove the current temporary file and
|
||||
stop the gstreamer element."""
|
||||
# Clear away the temp file.
|
||||
if self.filename and os.path.isfile(self.filename):
|
||||
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
|
||||
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
|
||||
width = height
|
||||
else:
|
||||
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,))
|
||||
}
|
||||
|
|
@ -1,645 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
import os
|
||||
# Used for automating chfn
|
||||
import pexpect
|
||||
# Used for copying files to ~/.face
|
||||
import shutil
|
||||
# Used for which command and checking for running processes.
|
||||
import subprocess
|
||||
# DBUS interface is used to update pidgin buddyicon when pidgin is running.
|
||||
import dbus
|
||||
|
||||
from gi.repository import Gtk, Gdk, GdkPixbuf # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot')
|
||||
|
||||
from mugshot_lib import Window
|
||||
from mugshot.CameraMugshotDialog import CameraMugshotDialog
|
||||
|
||||
username = os.getenv('USER')
|
||||
if not username:
|
||||
username = os.getenv('USERNAME')
|
||||
home = os.path.expanduser('~')
|
||||
libreoffice_prefs = os.path.join(home, '.config', 'libreoffice', '4', 'user',
|
||||
'registrymodifications.xcu')
|
||||
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.'''
|
||||
return subprocess.Popen(['which', command], \
|
||||
stdout=subprocess.PIPE).stdout.read().strip()
|
||||
|
||||
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,
|
||||
shell=True).stdout.read().strip()
|
||||
return int(n) > 0
|
||||
|
||||
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 ''
|
||||
value = entry_widget.get_text().strip()
|
||||
if value.lower() == 'none':
|
||||
value = ''
|
||||
return value
|
||||
|
||||
def get_confirmation_dialog(parent, primary_message, secondary_message,
|
||||
icon_name=None):
|
||||
"""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,
|
||||
message_format=primary_message)
|
||||
dialog.format_secondary_text(secondary_message)
|
||||
if icon_name:
|
||||
image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG)
|
||||
dialog.set_image(image)
|
||||
dialog.show_all()
|
||||
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()
|
||||
allocation = widget.get_allocation()
|
||||
window_pos = widget.get_window().get_position()
|
||||
# Align the left side of the menu with the left side of the button.
|
||||
x = window_pos[0] + allocation.x
|
||||
# Align the top of the menu with the bottom of the button.
|
||||
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):
|
||||
__gtype_name__ = "MugshotWindow"
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
"""Set up the main window"""
|
||||
super(MugshotWindow, self).finish_initializing(builder)
|
||||
|
||||
self.CameraDialog = CameraMugshotDialog
|
||||
|
||||
# User Image widgets
|
||||
self.image_button = builder.get_object('image_button')
|
||||
self.user_image = builder.get_object('user_image')
|
||||
self.image_menu = builder.get_object('image_menu')
|
||||
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 )
|
||||
|
||||
# Entry widgets (chfn)
|
||||
self.first_name_entry = builder.get_object('first_name')
|
||||
self.last_name_entry = builder.get_object('last_name')
|
||||
self.initials_entry = builder.get_object('initials')
|
||||
self.office_phone_entry = builder.get_object('office_phone')
|
||||
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')
|
||||
|
||||
# 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.
|
||||
logger.debug('Checking for ~/.face profile image')
|
||||
face = os.path.expanduser('~/.face')
|
||||
if os.path.isfile(face):
|
||||
self.set_user_image(face)
|
||||
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'):
|
||||
if line.startswith(username + ':'):
|
||||
logger.debug('Found details: %s' % line.strip())
|
||||
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)
|
||||
initials = first_name[0] + last_name[0]
|
||||
except:
|
||||
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 = ''
|
||||
|
||||
# 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)
|
||||
self.last_name_entry.set_text(self.last_name)
|
||||
self.initials_entry.set_text(self.initials)
|
||||
self.office_phone_entry.set_text(self.office_phone)
|
||||
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."""
|
||||
logger.debug("Setting user profile image to %s" % str(filename))
|
||||
if filename:
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
|
||||
scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
|
||||
self.user_image.set_from_pixbuf(scaled)
|
||||
else:
|
||||
self.user_image.set_from_icon_name('avatar-default', 128)
|
||||
|
||||
def filter_numbers(self, entry, *args):
|
||||
"""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
|
||||
changes."""
|
||||
logger.debug('Applying changes...')
|
||||
if self.get_chfn_details_updated():
|
||||
returns = self.save_chfn_details()
|
||||
|
||||
if self.get_libreoffice_details_updated():
|
||||
self.set_libreoffice_data()
|
||||
|
||||
if self.updated_image:
|
||||
self.save_image()
|
||||
|
||||
self.save_gsettings()
|
||||
|
||||
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',
|
||||
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."""
|
||||
logger.debug('Show photo menu')
|
||||
self.image_from_camera.set_visible(os.path.exists('/dev/video0'))
|
||||
if widget.get_active():
|
||||
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):
|
||||
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):
|
||||
logger.debug('Pidgin not installed or never opened, not updating.')
|
||||
return
|
||||
logger.debug('Prompting user to update pidgin buddy icon')
|
||||
update_pidgin = get_confirmation_dialog(self,
|
||||
_("Update Pidgin Buddy Icon?"),
|
||||
_("Would you also like to update your Pidgin buddy icon?"),
|
||||
'pidgin')
|
||||
if update_pidgin:
|
||||
if has_running_process('pidgin'):
|
||||
self.set_pidgin_buddyicon_dbus(filename)
|
||||
else:
|
||||
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",
|
||||
"/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...
|
||||
logger.debug('Updating pidgin buddy icon via xml')
|
||||
prefs_file = pidgin_prefs
|
||||
tmp_buffer = []
|
||||
if os.path.isfile(prefs_file):
|
||||
for line in open(prefs_file):
|
||||
if '<pref name=\'buddyicon\'' in line:
|
||||
new = line.split('value=')[0]
|
||||
if filename:
|
||||
new = new + 'value=\'%s\'/>\n' % filename
|
||||
else:
|
||||
new = new + 'value=\'\'/>\n'
|
||||
tmp_buffer.append(new)
|
||||
else:
|
||||
tmp_buffer.append(line)
|
||||
write_prefs = open(prefs_file, 'w')
|
||||
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."""
|
||||
logger.debug('Checking if chfn details have been modified.')
|
||||
if self.first_name != self.first_name_entry.get_text().strip() or \
|
||||
self.last_name != self.last_name_entry.get_text().strip() or \
|
||||
self.home_phone != self.home_phone_entry.get_text().strip() or \
|
||||
self.office_phone != self.office_phone_entry.get_text().strip():
|
||||
logger.debug('chfn details have been modified.')
|
||||
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)
|
||||
full_name = "%s %s" % (first_name, last_name)
|
||||
full_name = full_name.strip()
|
||||
office_phone = get_entry_value(self.office_phone_entry)
|
||||
if office_phone == '':
|
||||
office_phone = 'none'
|
||||
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))
|
||||
child.timeout = 5
|
||||
try:
|
||||
child.expect([".*ssword.*", pexpect.EOF])
|
||||
child.sendline(password)
|
||||
child.expect("Full Name.*:")
|
||||
child.sendline(full_name)
|
||||
for i in range(5):
|
||||
child.sendline('')
|
||||
except pexpect.TIMEOUT:
|
||||
# Password was incorrect, or sudo rights not granted
|
||||
logger.debug('Timeout reached, password was incorrect or sudo ' \
|
||||
'rights not granted.')
|
||||
pass
|
||||
child.close()
|
||||
if child.exitstatus == 0:
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
return_codes.append(child.exitstatus)
|
||||
|
||||
logger.debug('Attempting to set user details with chfn')
|
||||
child = pexpect.spawn('chfn')
|
||||
child.timeout = 5
|
||||
try:
|
||||
child.expect(['Password: ', pexpect.EOF])
|
||||
child.sendline(password)
|
||||
child.expect('Room Number.*:')
|
||||
child.sendline('')
|
||||
child.expect('Work Phone.*:')
|
||||
child.sendline(office_phone)
|
||||
child.expect('Home Phone.*:')
|
||||
child.sendline(home_phone)
|
||||
child.sendline(home_phone)
|
||||
except pexpect.TIMEOUT:
|
||||
logger.debug('Timeout reached, password was likely incorrect.')
|
||||
child.close(True)
|
||||
if child.exitstatus == 0:
|
||||
self.office_phone = office_phone
|
||||
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."""
|
||||
# Return False if there is no preferences file.
|
||||
if not os.path.isfile(libreoffice_prefs):
|
||||
logger.debug('LibreOffice is not installed or has not been opened.'
|
||||
' Not updating.')
|
||||
return False
|
||||
# Compare the current entries to the existing LibreOffice data.
|
||||
data = self.get_libreoffice_data()
|
||||
if data['first_name'] != get_entry_value(self.first_name_entry):
|
||||
return True
|
||||
if data['last_name'] != get_entry_value(self.last_name_entry):
|
||||
return True
|
||||
if data['initials'] != get_entry_value(self.initials_entry):
|
||||
return True
|
||||
if data['email'] != get_entry_value(self.email_entry):
|
||||
return True
|
||||
if data['home_phone'] != get_entry_value(self.home_phone_entry):
|
||||
return True
|
||||
if data['office_phone'] != get_entry_value(self.office_phone_entry):
|
||||
return True
|
||||
if data['fax'] != get_entry_value(self.fax_entry):
|
||||
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': '',
|
||||
'home_phone': '', 'office_phone': '', 'fax': ''}
|
||||
if os.path.isfile(prefs_file):
|
||||
logger.debug('Getting settings from %s' % prefs_file)
|
||||
for line in open(prefs_file):
|
||||
if "UserProfile/Data" in line:
|
||||
value = line.split('<value>')[1].split('</value>')[0]
|
||||
value = value.strip()
|
||||
# First Name
|
||||
if 'name="givenname"' in line:
|
||||
data['first_name'] = value
|
||||
# Last Name
|
||||
elif 'name="sn"' in line:
|
||||
data['last_name'] = value
|
||||
# Initials
|
||||
elif 'name="initials"' in line:
|
||||
data['initials'] = value
|
||||
# Email
|
||||
elif 'name="mail"' in line:
|
||||
data['email'] = value
|
||||
# Home Phone
|
||||
elif 'name="homephone"' in line:
|
||||
data['home_phone'] = value
|
||||
# Office Phone
|
||||
elif 'name="telephonenumber"' in line:
|
||||
data['office_phone'] = value
|
||||
# Fax Number
|
||||
elif 'name="facsimiletelephonenumber"' in line:
|
||||
data['fax'] = value
|
||||
else:
|
||||
pass
|
||||
return data
|
||||
|
||||
def set_libreoffice_data(self):
|
||||
"""Update the LibreOffice registymodifications preferences file."""
|
||||
prefs_file = libreoffice_prefs
|
||||
if os.path.isfile(prefs_file):
|
||||
logger.debug('Prompting user to update LibreOffice details.')
|
||||
update_libreoffice = get_confirmation_dialog(self,
|
||||
_("Update LibreOffice User Details?"),
|
||||
_("Would you also like to update your user "
|
||||
"details in LibreOffice?"),
|
||||
'libreoffice-startcenter')
|
||||
if update_libreoffice:
|
||||
logger.debug('Confirm: Updating details.')
|
||||
tmp_buffer = []
|
||||
for line in open(prefs_file):
|
||||
new = None
|
||||
if "UserProfile/Data" in line:
|
||||
new = line.split('<value>')[0]
|
||||
# First Name
|
||||
if 'name="givenname"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.first_name_entry)
|
||||
# Last Name
|
||||
elif 'name="sn"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.last_name_entry)
|
||||
# Initials
|
||||
elif 'name="initials"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.initials_entry)
|
||||
# Email
|
||||
elif 'name="mail"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.email_entry)
|
||||
# Home Phone
|
||||
elif 'name="homephone"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.home_phone_entry)
|
||||
# Office Phone
|
||||
elif 'name="telephonenumber"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.office_phone_entry)
|
||||
# Fax Number
|
||||
elif 'name="facsimiletelephonenumber"' in line:
|
||||
new = new + '<value>%s</value></prop></item>\n' % \
|
||||
get_entry_value(self.fax_entry)
|
||||
else:
|
||||
new = line
|
||||
tmp_buffer.append(new)
|
||||
else:
|
||||
tmp_buffer.append(line)
|
||||
open_prefs = open(prefs_file, 'w')
|
||||
for line in tmp_buffer:
|
||||
open_prefs.write(line)
|
||||
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
|
||||
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.
|
||||
model = self.iconview.get_model()
|
||||
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'):
|
||||
full_path = os.path.join('/usr/share/pixmaps/faces/', filename)
|
||||
if os.path.isfile(full_path):
|
||||
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
|
||||
selected photo and set it to the user profile image."""
|
||||
selected_items = self.iconview.get_selected_items()
|
||||
if len(selected_items) != 0:
|
||||
# Get the filename from the stock browser iconview.
|
||||
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."""
|
||||
# Initialize a GtkFileChooserDialog.
|
||||
chooser = Gtk.FileChooserDialog(_("Select an image"), self,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_OK, Gtk.ResponseType.OK))
|
||||
|
||||
# Add a filter for only image files.
|
||||
image_filter = Gtk.FileFilter()
|
||||
image_filter.set_name('Images')
|
||||
image_filter.add_mime_type('image/*')
|
||||
chooser.add_filter(image_filter)
|
||||
|
||||
# Run the dialog, grab the filename if confirmed, then hide the dialog.
|
||||
response = chooser.run()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
# Update the user image, store the path for committing later.
|
||||
self.updated_image = chooser.get_filename()
|
||||
logger.debug("Selected %s" % self.updated_image)
|
||||
self.set_user_image(self.updated_image)
|
||||
chooser.hide()
|
||||
|
||||
# = Password Entry ======================================================= #
|
||||
def get_password(self):
|
||||
"""Display a password dialog for authenticating to sudo and chfn."""
|
||||
logger.debug("Prompting user for password")
|
||||
dialog = self.builder.get_object('password_dialog')
|
||||
entry = self.builder.get_object('password_entry')
|
||||
response = dialog.run()
|
||||
dialog.hide()
|
||||
if response == Gtk.ResponseType.OK:
|
||||
logger.debug("Password entered")
|
||||
pw = entry.get_text()
|
||||
entry.set_text('')
|
||||
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)
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import optparse
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
|
||||
from mugshot import MugshotWindow
|
||||
|
||||
from mugshot_lib import set_up_logging, get_version
|
||||
|
||||
def parse_options():
|
||||
"""Support for command line options"""
|
||||
parser = optparse.OptionParser(version="%%prog %s" % get_version())
|
||||
parser.add_option(
|
||||
"-v", "--verbose", action="count", dest="verbose",
|
||||
help=_("Show debug messages (-vv debugs mugshot_lib also)"))
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
set_up_logging(options)
|
||||
|
||||
def main():
|
||||
'constructor for your class instances'
|
||||
parse_options()
|
||||
|
||||
# Run the application.
|
||||
window = MugshotWindow.MugshotWindow()
|
||||
window.show()
|
||||
Gtk.main()
|
|
@ -1,325 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
'''Enhances builder connections, provides object to access glade objects'''
|
||||
|
||||
from gi.repository import GObject, Gtk # pylint: disable=E0611
|
||||
|
||||
import inspect
|
||||
import functools
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot_lib')
|
||||
|
||||
from xml.etree.cElementTree import ElementTree
|
||||
|
||||
# this module is big so uses some conventional prefixes and postfixes
|
||||
# *s list, except self.widgets is a dictionary
|
||||
# *_dict dictionary
|
||||
# *name string
|
||||
# ele_* element in a ElementTree
|
||||
|
||||
|
||||
# pylint: disable=R0904
|
||||
# the many public methods is a feature of Gtk.Builder
|
||||
class Builder(Gtk.Builder):
|
||||
''' extra features
|
||||
connects glade defined handler to default_handler if necessary
|
||||
auto connects widget to handler with matching name or alias
|
||||
auto connects several widgets to a handler via multiple aliases
|
||||
allow handlers to lookup widget name
|
||||
logs every connection made, and any on_* not made
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
Gtk.Builder.__init__(self)
|
||||
self.widgets = {}
|
||||
self.glade_handler_dict = {}
|
||||
self.connections = []
|
||||
self._reverse_widget_dict = {}
|
||||
|
||||
# pylint: disable=R0201
|
||||
# this is a method so that a subclass of Builder can redefine it
|
||||
def default_handler(self,
|
||||
handler_name, filename, *args, **kwargs):
|
||||
'''helps the apprentice guru
|
||||
|
||||
glade defined handlers that do not exist come here instead.
|
||||
An apprentice guru might wonder which signal does what he wants,
|
||||
now he can define any likely candidates in glade and notice which
|
||||
ones get triggered when he plays with the project.
|
||||
this method does not appear in Gtk.Builder'''
|
||||
logger.debug('''tried to call non-existent function:%s()
|
||||
expected in %s
|
||||
args:%s
|
||||
kwargs:%s''', handler_name, filename, args, kwargs)
|
||||
# pylint: enable=R0201
|
||||
|
||||
def get_name(self, widget):
|
||||
''' allows a handler to get the name (id) of a widget
|
||||
|
||||
this method does not appear in Gtk.Builder'''
|
||||
return self._reverse_widget_dict.get(widget)
|
||||
|
||||
def add_from_file(self, filename):
|
||||
'''parses xml file and stores wanted details'''
|
||||
Gtk.Builder.add_from_file(self, filename)
|
||||
|
||||
# extract data for the extra interfaces
|
||||
tree = ElementTree()
|
||||
tree.parse(filename)
|
||||
|
||||
ele_widgets = tree.getiterator("object")
|
||||
for ele_widget in ele_widgets:
|
||||
name = ele_widget.attrib['id']
|
||||
widget = self.get_object(name)
|
||||
|
||||
# populate indexes - a dictionary of widgets
|
||||
self.widgets[name] = widget
|
||||
|
||||
# populate a reversed dictionary
|
||||
self._reverse_widget_dict[widget] = name
|
||||
|
||||
# populate connections list
|
||||
ele_signals = ele_widget.findall("signal")
|
||||
|
||||
connections = [
|
||||
(name,
|
||||
ele_signal.attrib['name'],
|
||||
ele_signal.attrib['handler']) for ele_signal in ele_signals]
|
||||
|
||||
if connections:
|
||||
self.connections.extend(connections)
|
||||
|
||||
ele_signals = tree.getiterator("signal")
|
||||
for ele_signal in ele_signals:
|
||||
self.glade_handler_dict.update(
|
||||
{ele_signal.attrib["handler"]: None})
|
||||
|
||||
def connect_signals(self, callback_obj):
|
||||
'''connect the handlers defined in glade
|
||||
|
||||
reports successful and failed connections
|
||||
and logs call to missing handlers'''
|
||||
filename = inspect.getfile(callback_obj.__class__)
|
||||
callback_handler_dict = dict_from_callback_obj(callback_obj)
|
||||
connection_dict = {}
|
||||
connection_dict.update(self.glade_handler_dict)
|
||||
connection_dict.update(callback_handler_dict)
|
||||
for item in connection_dict.items():
|
||||
if item[1] is None:
|
||||
# the handler is missing so reroute to default_handler
|
||||
handler = functools.partial(
|
||||
self.default_handler, item[0], filename)
|
||||
|
||||
connection_dict[item[0]] = handler
|
||||
|
||||
# replace the run time warning
|
||||
logger.warn("expected handler '%s' in %s",
|
||||
item[0], filename)
|
||||
|
||||
# connect glade define handlers
|
||||
Gtk.Builder.connect_signals(self, connection_dict)
|
||||
|
||||
# let's tell the user how we applied the glade design
|
||||
for connection in self.connections:
|
||||
widget_name, signal_name, handler_name = connection
|
||||
logger.debug("connect builder by design '%s', '%s', '%s'",
|
||||
widget_name, signal_name, handler_name)
|
||||
|
||||
def get_ui(self, callback_obj=None, by_name=True):
|
||||
'''Creates the ui object with widgets as attributes
|
||||
|
||||
connects signals by 2 methods
|
||||
this method does not appear in Gtk.Builder'''
|
||||
|
||||
result = UiFactory(self.widgets)
|
||||
|
||||
# Hook up any signals the user defined in glade
|
||||
if callback_obj is not None:
|
||||
# connect glade define handlers
|
||||
self.connect_signals(callback_obj)
|
||||
|
||||
if by_name:
|
||||
auto_connect_by_name(callback_obj, self)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# pylint: disable=R0903
|
||||
# this class deliberately does not provide any public interfaces
|
||||
# apart from the glade widgets
|
||||
class UiFactory():
|
||||
''' provides an object with attributes as glade widgets'''
|
||||
def __init__(self, widget_dict):
|
||||
self._widget_dict = widget_dict
|
||||
for (widget_name, widget) in 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():
|
||||
pyname = make_pyname(widget_name)
|
||||
if pyname != widget_name:
|
||||
if hasattr(self, pyname):
|
||||
logger.debug(cannot_message, pyname, widget_name)
|
||||
else:
|
||||
logger.debug(consider_message, widget_name)
|
||||
setattr(self, pyname, widget)
|
||||
|
||||
def iterator():
|
||||
'''Support 'for o in self' '''
|
||||
return iter(widget_dict.values())
|
||||
setattr(self, '__iter__', iterator)
|
||||
|
||||
def __getitem__(self, name):
|
||||
'access as dictionary where name might be non-pythonic'
|
||||
return self._widget_dict[name]
|
||||
# pylint: enable=R0903
|
||||
|
||||
|
||||
def make_pyname(name):
|
||||
''' mangles non-pythonic names into pythonic ones'''
|
||||
pyname = ''
|
||||
for character in name:
|
||||
if (character.isalpha() or character == '_' or
|
||||
(pyname and character.isdigit())):
|
||||
pyname += character
|
||||
else:
|
||||
pyname += '_'
|
||||
return pyname
|
||||
|
||||
|
||||
# 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):
|
||||
members = []
|
||||
for k in dir(obj):
|
||||
try:
|
||||
attr = getattr(obj, k)
|
||||
except:
|
||||
continue
|
||||
if check(attr):
|
||||
members.append((k, attr))
|
||||
members.sort()
|
||||
return members
|
||||
|
||||
|
||||
def dict_from_callback_obj(callback_obj):
|
||||
'''a dictionary interface to callback_obj'''
|
||||
methods = getmembers(callback_obj, inspect.ismethod)
|
||||
|
||||
aliased_methods = [x[1] for x in methods if hasattr(x[1], 'aliases')]
|
||||
|
||||
# a method may have several aliases
|
||||
#~ @alias('on_btn_foo_clicked')
|
||||
#~ @alias('on_tool_foo_activate')
|
||||
#~ on_menu_foo_activate():
|
||||
#~ pass
|
||||
alias_groups = [(x.aliases, x) for x in aliased_methods]
|
||||
|
||||
aliases = []
|
||||
for item in alias_groups:
|
||||
for alias in item[0]:
|
||||
aliases.append((alias, item[1]))
|
||||
|
||||
dict_methods = dict(methods)
|
||||
dict_aliases = dict(aliases)
|
||||
|
||||
results = {}
|
||||
results.update(dict_methods)
|
||||
results.update(dict_aliases)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def auto_connect_by_name(callback_obj, builder):
|
||||
'''finds handlers like on_<widget_name>_<signal> and connects them
|
||||
|
||||
i.e. find widget,signal pair in builder and call
|
||||
widget.connect(signal, on_<widget_name>_<signal>)'''
|
||||
|
||||
callback_handler_dict = dict_from_callback_obj(callback_obj)
|
||||
|
||||
for item in builder.widgets.items():
|
||||
(widget_name, widget) = item
|
||||
signal_ids = []
|
||||
try:
|
||||
widget_type = type(widget)
|
||||
while widget_type:
|
||||
signal_ids.extend(GObject.signal_list_ids(widget_type))
|
||||
widget_type = GObject.type_parent(widget_type)
|
||||
except RuntimeError: # pylint wants a specific error
|
||||
pass
|
||||
signal_names = [GObject.signal_name(sid) for sid in signal_ids]
|
||||
|
||||
# Now, automatically find any the user didn't specify in glade
|
||||
for sig in signal_names:
|
||||
# using convention suggested by glade
|
||||
sig = sig.replace("-", "_")
|
||||
handler_names = ["on_%s_%s" % (widget_name, sig)]
|
||||
|
||||
# Using the convention that the top level window is not
|
||||
# specified in the handler name. That is use
|
||||
# on_destroy() instead of on_windowname_destroy()
|
||||
if widget is callback_obj:
|
||||
handler_names.append("on_%s" % sig)
|
||||
|
||||
do_connect(item, sig, handler_names,
|
||||
callback_handler_dict, builder.connections)
|
||||
|
||||
log_unconnected_functions(callback_handler_dict, builder.connections)
|
||||
|
||||
|
||||
def do_connect(item, signal_name, handler_names,
|
||||
callback_handler_dict, connections):
|
||||
'''connect this signal to an unused handler'''
|
||||
widget_name, widget = item
|
||||
|
||||
for handler_name in handler_names:
|
||||
target = handler_name in callback_handler_dict.keys()
|
||||
connection = (widget_name, signal_name, handler_name)
|
||||
duplicate = connection in connections
|
||||
if target and not duplicate:
|
||||
widget.connect(signal_name, callback_handler_dict[handler_name])
|
||||
connections.append(connection)
|
||||
|
||||
logger.debug("connect builder by name '%s','%s', '%s'",
|
||||
widget_name, signal_name, handler_name)
|
||||
|
||||
|
||||
def log_unconnected_functions(callback_handler_dict, connections):
|
||||
'''log functions like on_* that we could not connect'''
|
||||
|
||||
connected_functions = [x[2] for x in connections]
|
||||
|
||||
handler_names = callback_handler_dict.keys()
|
||||
unconnected = [x for x in handler_names if x.startswith('on_')]
|
||||
|
||||
for handler_name in connected_functions:
|
||||
try:
|
||||
unconnected.remove(handler_name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for handler_name in unconnected:
|
||||
logger.debug("Not connected to builder '%s'", handler_name)
|
|
@ -1,61 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
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):
|
||||
__gtype_name__ = "CameraDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""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')
|
||||
new_object = builder.get_object("camera_mugshot_dialog")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initalizing should be called after parsing the ui definition
|
||||
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.
|
||||
"""
|
||||
|
||||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self, True)
|
||||
|
||||
# code for other initialization actions should be added here
|
||||
|
||||
def on_btn_close_clicked(self, widget, data=None):
|
||||
self.destroy()
|
||||
|
||||
def on_btn_help_clicked(self, widget, data=None):
|
||||
show_uri(self, "ghelp:%s" % get_help_uri('preferences'))
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
from gi.repository import Gio, Gtk # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot_lib')
|
||||
|
||||
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):
|
||||
__gtype_name__ = "Window"
|
||||
|
||||
# To construct a new instance of this method, the following notable
|
||||
# methods are called in this order:
|
||||
# __new__(cls)
|
||||
# __init__(self)
|
||||
# finish_initializing(self, builder)
|
||||
# __init__(self)
|
||||
#
|
||||
# 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
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated BaseMugshotWindow object.
|
||||
"""
|
||||
builder = get_builder('MugshotWindow')
|
||||
new_object = builder.get_object("mugshot_window")
|
||||
new_object.finish_initializing(builder)
|
||||
return new_object
|
||||
|
||||
def finish_initializing(self, builder):
|
||||
"""Called while initializing this instance in __new__
|
||||
|
||||
finish_initializing should be called after parsing the UI definition
|
||||
and creating a MugshotWindow object with it in order to finish
|
||||
initializing the start of the new MugshotWindow instance.
|
||||
"""
|
||||
# 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.settings = Gio.Settings("apps.mugshot")
|
||||
self.settings.connect('changed', self.on_preferences_changed)
|
||||
|
||||
def on_help_activate(self, widget, data=None):
|
||||
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:
|
||||
logger.debug('show existing camera_dialog')
|
||||
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.connect('apply', self.on_camera_dialog_apply)
|
||||
self.camera_dialog.show()
|
||||
|
||||
def on_destroy(self, widget, data=None):
|
||||
"""Called when the MugshotWindow is closed."""
|
||||
# Clean up code for saving application state should be added here.
|
||||
Gtk.main_quit()
|
||||
|
||||
def on_preferences_changed(self, settings, key, data=None):
|
||||
logger.debug('preference changed: %s = %s' % (key, str(settings.get_value(key))))
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
'''facade - makes mugshot_lib package easy to refactor
|
||||
|
||||
while keeping its api constant'''
|
||||
from . helpers import set_up_logging
|
||||
from . Window import Window
|
||||
from . mugshotconfig import get_version
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
"""Helpers for an Ubuntu application."""
|
||||
import logging
|
||||
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
|
||||
file
|
||||
|
||||
:param builder_file_name: The name of the builder file, without extension.
|
||||
Assumed to be in the 'ui' directory under the data path.
|
||||
"""
|
||||
# Look for the ui file that describes the user interface.
|
||||
ui_filename = get_data_file('ui', '%s.ui' % (builder_file_name,))
|
||||
if not os.path.exists(ui_filename):
|
||||
ui_filename = None
|
||||
|
||||
builder = Builder()
|
||||
builder.set_translation_domain('mugshot')
|
||||
builder.add_from_file(ui_filename)
|
||||
return builder
|
||||
|
||||
|
||||
# Owais Lone : To get quick access to icons and stuff.
|
||||
def get_media_file(media_file_name):
|
||||
media_filename = get_data_file('media', '%s' % (media_file_name,))
|
||||
if not os.path.exists(media_filename):
|
||||
media_filename = None
|
||||
|
||||
return "file:///"+media_filename
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def set_up_logging(opts):
|
||||
# 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'")
|
||||
|
||||
logger = logging.getLogger('mugshot')
|
||||
logger_sh = logging.StreamHandler()
|
||||
logger_sh.setFormatter(formatter)
|
||||
logger.addHandler(logger_sh)
|
||||
|
||||
lib_logger = logging.getLogger('mugshot_lib')
|
||||
lib_logger_sh = logging.StreamHandler()
|
||||
lib_logger_sh.setFormatter(formatter)
|
||||
lib_logger.addHandler(lib_logger_sh)
|
||||
|
||||
# Set the logging level to show debug messages.
|
||||
if opts.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.debug('logging enabled')
|
||||
if opts.verbose > 1:
|
||||
lib_logger.setLevel(logging.DEBUG)
|
||||
|
||||
def get_help_uri(page=None):
|
||||
# help_uri from source tree - default language
|
||||
here = os.path.dirname(__file__)
|
||||
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
|
||||
|
||||
if not os.path.exists(help_uri):
|
||||
# installed so use gnome help tree - user's language
|
||||
help_uri = 'mugshot'
|
||||
|
||||
# unspecified page is the index.page
|
||||
if page is not None:
|
||||
help_uri = '%s#%s' % (help_uri, page)
|
||||
|
||||
return help_uri
|
||||
|
||||
def show_uri(parent, link):
|
||||
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):
|
||||
'''attach alternative_function_name(s) to function'''
|
||||
if not hasattr(function, 'aliases'):
|
||||
function.aliases = []
|
||||
function.aliases.append(alternative_function_name)
|
||||
return function
|
||||
return decorator
|
|
@ -1,69 +0,0 @@
|
|||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
__all__ = [
|
||||
'project_path_not_found',
|
||||
'get_data_file',
|
||||
'get_data_path',
|
||||
]
|
||||
|
||||
# Where your project will look for your data (for instance, images and ui
|
||||
# files). By default, this is ../data, relative your trunk layout
|
||||
__mugshot_data_directory__ = '../data/'
|
||||
__license__ = 'GPL-3'
|
||||
__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."""
|
||||
|
||||
|
||||
def get_data_file(*path_segments):
|
||||
"""Get the full path to a data file.
|
||||
|
||||
Returns the path to a file underneath the data directory (as defined by
|
||||
`get_data_path`). Equivalent to os.path.join(get_data_path(),
|
||||
*path_segments).
|
||||
"""
|
||||
return os.path.join(get_data_path(), *path_segments)
|
||||
|
||||
|
||||
def get_data_path():
|
||||
"""Retrieve mugshot data path
|
||||
|
||||
This path is by default <mugshot_lib_path>/../data/ in trunk
|
||||
and /usr/share/mugshot in an installed version but this path
|
||||
is specified at installation time.
|
||||
"""
|
||||
|
||||
# Get pathname absolute or relative.
|
||||
path = os.path.join(
|
||||
os.path.dirname(__file__), __mugshot_data_directory__)
|
||||
|
||||
abs_data_path = os.path.abspath(path)
|
||||
if not os.path.exists(abs_data_path):
|
||||
raise project_path_not_found
|
||||
|
||||
return abs_data_path
|
||||
|
||||
|
||||
def get_version():
|
||||
return __version__
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
import locale
|
||||
locale.textdomain('mugshot')
|
||||
|
||||
# Add project root directory (enable symlink and trunk execution)
|
||||
PROJECT_ROOT_DIRECTORY = os.path.abspath(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
|
||||
|
||||
python_path = []
|
||||
if os.path.abspath(__file__).startswith('/opt'):
|
||||
locale.bindtextdomain('mugshot', '/opt/extras.ubuntu.com/mugshot/share/locale')
|
||||
syspath = sys.path[:] # copy to avoid infinite loop in pending objects
|
||||
for path in syspath:
|
||||
opt_path = path.replace('/usr', '/opt/extras.ubuntu.com/mugshot')
|
||||
python_path.insert(0, opt_path)
|
||||
sys.path.insert(0, opt_path)
|
||||
os.putenv("XDG_DATA_DIRS", "%s:%s" % ("/opt/extras.ubuntu.com/mugshot/share/", os.getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")))
|
||||
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'mugshot'))
|
||||
and PROJECT_ROOT_DIRECTORY not in sys.path):
|
||||
python_path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
|
||||
if python_path:
|
||||
os.putenv('PYTHONPATH', "%s:%s" % (os.getenv('PYTHONPATH', ''), ':'.join(python_path))) # for subprocesses
|
||||
|
||||
import mugshot
|
||||
mugshot.main()
|
|
@ -1,8 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=About Me
|
||||
Comment=Configure your profile image and contact details
|
||||
Categories=XFCE;GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-PersonalSettings;DesktopSettings;X-XFCE;
|
||||
Exec=mugshot
|
||||
Icon=mugshot
|
||||
Terminal=false
|
||||
Type=Application
|
|
@ -1,41 +1,3 @@
|
|||
mugshot (13.07ubuntu1) UNRELEASED; urgency=low
|
||||
|
||||
* Fix setup.py, update debian/control
|
||||
* Add translations
|
||||
* Update messages
|
||||
* Added camera error logging for failed init
|
||||
* Do not show stock browser if no faces directory. Show error message
|
||||
if camera fails to init.
|
||||
* Small fixes and lots of comments
|
||||
* Remove temp file when exiting, show camera option only if camera is
|
||||
installed
|
||||
* Introduction of working camera code.
|
||||
* Initial camera, inspired by Rick Spencer's quidgets.
|
||||
* Added mugshot.svg by Simon Steinbeiß
|
||||
* Use old username to always work.
|
||||
* Creating ubuntu package
|
||||
* More debug, fix for libreoffice confirmation
|
||||
* Update desktop file and version number
|
||||
* Remove about dialog, connect the help button.
|
||||
* Improved usability and additional cleanup.
|
||||
* Add prompts for updating Pidgin/LibreOffice
|
||||
* Discard preferences dialog
|
||||
* Add COPYING
|
||||
* Store additional entries in dconf
|
||||
* Add number filter and libreoffice settings.
|
||||
* Set pidgin profile image when changing image.
|
||||
* Improve default widgets, further cleanup
|
||||
* Initial code cleanup and bug fixes.
|
||||
* Initial commit with functionality.
|
||||
|
||||
-- Sean Davis <smd.seandavis@gmail.com> Wed, 17 Jul 2013 12:53:22 -0400
|
||||
|
||||
mugshot (13.07) raring; urgency=low
|
||||
|
||||
* Fedora fixes for LibreOffice and chfn
|
||||
|
||||
-- Sean Davis <smd.seandavis@gmail.com> Wed, 17 Jul 2013 12:53:22 -0400
|
||||
|
||||
mugshot (0.1) raring; urgency=low
|
||||
|
||||
* Initial release.
|
||||
|
|
|
@ -23,5 +23,10 @@ Depends: ${misc:Depends},
|
|||
yelp
|
||||
Suggests:
|
||||
gstreamer1.0-plugins-good
|
||||
Description: UNKNOWN
|
||||
UNKNOWN
|
||||
Description: a lightweight user-configuration application
|
||||
Mugshot allows you to easily update personal user details. This includes:
|
||||
.
|
||||
Linux profile image: ~/.face
|
||||
User details stored in /etc/passwd (used by finger)
|
||||
Pidgin buddy icon
|
||||
LibreOffice user details
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import unittest
|
||||
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
#from mugshot import AboutMugshotDialog
|
||||
|
||||
class TestExample(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
# Keeping the below items for reference later.
|
||||
#self.AboutMugshotDialog_members = [
|
||||
#'AboutDialog', 'AboutMugshotDialog', 'gettext', 'logger', 'logging']
|
||||
|
||||
#def test_AboutMugshotDialog_members(self):
|
||||
# all_members = dir(AboutMugshotDialog)
|
||||
# public_members = [x for x in all_members if not x.startswith('_')]
|
||||
# public_members.sort()
|
||||
# self.assertEqual(self.AboutMugshotDialog_members, public_members)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- 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
|
||||
# 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
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
import unittest
|
||||
import subprocess
|
||||
|
||||
class TestPylint(unittest.TestCase):
|
||||
def test_project_errors_only(self):
|
||||
'''run pylint in error only mode
|
||||
|
||||
your code may well work even with pylint errors
|
||||
but have some unusual code'''
|
||||
return_code = subprocess.call(["pylint", '-E', 'mugshot'])
|
||||
# not needed because nosetests displays pylint console output
|
||||
#self.assertEqual(return_code, 0)
|
||||
|
||||
# un-comment the following for loads of diagnostics
|
||||
#~ def test_project_full_report(self):
|
||||
#~ '''Only for the brave
|
||||
#~
|
||||
#~ you will have to make judgement calls about your code standards
|
||||
#~ that differ from the norm'''
|
||||
#~ return_code = subprocess.call(["pylint", 'mugshot'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
'you will get better results with nosetests'
|
||||
unittest.main()
|
Loading…
Reference in New Issue