Python cleanup
This commit is contained in:
parent
653fc5a108
commit
527fa8cf4c
|
@ -3,16 +3,16 @@
|
|||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# Copyright (C) 2010 Rick Spencer <rick.spencer@canonical.com>
|
||||
# Portions of this code are inspired by web_cam_box by Rick Spencer.
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -21,65 +21,69 @@ from locale import gettext as _
|
|||
import logging
|
||||
logger = logging.getLogger('mugshot')
|
||||
|
||||
from gi.repository import Gtk, GdkX11, GObject, Gst, GstVideo, GdkPixbuf
|
||||
from gi.repository import Gtk, GObject, Gst, GdkPixbuf
|
||||
import cairo
|
||||
|
||||
import tempfile, os
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
from mugshot_lib.CameraDialog import CameraDialog
|
||||
|
||||
|
||||
def draw_message(widget, message, ctx):
|
||||
"""Draw a message (including newlines) vertically centered on a cairo context."""
|
||||
"""Draw a message (including newlines) vertically centered on a cairo
|
||||
context."""
|
||||
split_msg = message.split('\n')
|
||||
|
||||
|
||||
# Get the height and width of the drawing area.
|
||||
alloc = widget.get_allocation()
|
||||
height = alloc.height
|
||||
width = alloc.width
|
||||
|
||||
|
||||
# Make the background black.
|
||||
ctx.set_source_rgb(0,0,0)
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.paint()
|
||||
|
||||
|
||||
# Set the font details.
|
||||
font_size = 20
|
||||
font_color = (255,255,255)
|
||||
font_color = (255, 255, 255)
|
||||
font_name = "Sans"
|
||||
row_spacing = 6
|
||||
left_spacing = 10
|
||||
|
||||
|
||||
# Get start position
|
||||
message_height = (len(split_msg)*font_size)+len(split_msg)-1-14
|
||||
current_pos = (height-message_height)/2
|
||||
message_height = (len(split_msg) * font_size) + len(split_msg) - 15
|
||||
current_pos = (height - message_height) / 2
|
||||
|
||||
# Draw the message to the drawing area.
|
||||
ctx.set_source_rgb(*font_color)
|
||||
ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL,
|
||||
cairo.FONT_WEIGHT_NORMAL)
|
||||
ctx.set_font_size(font_size)
|
||||
|
||||
|
||||
for line in split_msg:
|
||||
ctx.move_to(left_spacing,current_pos)
|
||||
ctx.move_to(left_spacing, current_pos)
|
||||
ctx.show_text(line)
|
||||
current_pos = current_pos + font_size + row_spacing
|
||||
|
||||
|
||||
class CameraMugshotDialog(CameraDialog):
|
||||
"""Camera Capturing Dialog"""
|
||||
__gtype_name__ = "CameraMugshotDialog"
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
"""Set up the camera dialog"""
|
||||
super(CameraMugshotDialog, self).finish_initializing(builder)
|
||||
|
||||
|
||||
# Initialize Gst or nothing will work.
|
||||
Gst.init(None)
|
||||
|
||||
# Pack the video widget into the dialog.
|
||||
vbox = builder.get_object('camera_box')
|
||||
self.video_window = Gtk.DrawingArea()
|
||||
self.video_window.connect("realize",self.__on_video_window_realized)
|
||||
self.video_window.connect("realize", self.__on_video_window_realized)
|
||||
vbox.pack_start(self.video_window, True, True, 0)
|
||||
self.video_window.show()
|
||||
|
||||
|
||||
# Prepare the camerabin element.
|
||||
self.camerabin = Gst.ElementFactory.make("camerabin", "camera-source")
|
||||
if self.camerabin:
|
||||
|
@ -94,55 +98,62 @@ class CameraMugshotDialog(CameraDialog):
|
|||
else:
|
||||
devices = []
|
||||
for device in os.listdir('/dev/'):
|
||||
if device.startswith('video'): devices.append(device)
|
||||
logger.error(_('Camera failed to load. Devices: %s') % '; '.join(devices))
|
||||
self.draw_handler = self.video_window.connect('draw', self.on_failed_draw)
|
||||
if device.startswith('video'):
|
||||
devices.append(device)
|
||||
logger.error(_('Camera failed to load. Devices: %s') %
|
||||
'; '.join(devices))
|
||||
self.draw_handler = self.video_window.connect('draw',
|
||||
self.on_failed_draw)
|
||||
self.realized = True
|
||||
|
||||
|
||||
# Essential widgets
|
||||
self.record_button = builder.get_object('camera_record')
|
||||
self.apply_button = builder.get_object('camera_apply')
|
||||
|
||||
|
||||
# Store the temporary filename to be used.
|
||||
self.filename = None
|
||||
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def on_failed_draw(self, widget, ctx):
|
||||
"""Display a message that the camera failed to load."""
|
||||
# Translators: Please include newlines, as required to fit the message.
|
||||
message = _("Sorry, but your camera\nfailed to initialize.")
|
||||
draw_message(widget, message, ctx)
|
||||
|
||||
|
||||
def on_draw(self, widget, ctx):
|
||||
"""Display a message that the camera is initializing on first draw.
|
||||
Afterwards, blank the drawing area to clear the message."""
|
||||
# Translators: Please include newlines, as required to fit the message.
|
||||
message = _("Please wait while your\ncamera is initialized.")
|
||||
draw_message(widget, message, ctx)
|
||||
|
||||
|
||||
# Redefine on_draw to blank the drawing area next time.
|
||||
def on_draw(self, widget, ctx):
|
||||
ctx.set_source_rgb(0,0,0)
|
||||
"""Redefinition of on_draw to blank the drawing area next time."""
|
||||
ctx.set_source_rgb(0, 0, 0)
|
||||
ctx.paint()
|
||||
|
||||
# Redefine on_draw once more to do nothing else.
|
||||
def on_draw(self, widget, ctx):
|
||||
"""Redefinition of on_draw no longer do anything."""
|
||||
pass
|
||||
|
||||
|
||||
def play(self):
|
||||
"""Start the camera streaming and display the output. It is necessary
|
||||
"""Start the camera streaming and display the output. It is necessary
|
||||
to start the camera playing before using most other functions."""
|
||||
if not self.realized:
|
||||
self._set_video_window_id()
|
||||
if not self.realized:
|
||||
logger.error(_("Cannot display camera output. Ignoring play command"))
|
||||
logger.error(_("Cannot display camera output."
|
||||
"Ignoring play command"))
|
||||
else:
|
||||
if self.camerabin:
|
||||
self.camerabin.set_state(Gst.State.PLAYING)
|
||||
|
||||
def pause(self):
|
||||
"""Pause the camera output. It will cause the image to "freeze".
|
||||
Use play() to start the camera playing again. Note that calling pause
|
||||
"""Pause the camera output. It will cause the image to "freeze".
|
||||
Use play() to start the camera playing again. Note that calling pause
|
||||
before play may cause errors on certain camera."""
|
||||
if self.camerabin:
|
||||
self.camerabin.set_state(Gst.State.PAUSED)
|
||||
|
@ -150,11 +161,11 @@ class CameraMugshotDialog(CameraDialog):
|
|||
def take_picture(self, filename):
|
||||
"""take_picture - grab a frame from the webcam and save it to
|
||||
'filename.
|
||||
|
||||
|
||||
If play is not called before take_picture,
|
||||
an error may occur. If take_picture is called immediately after play,
|
||||
the camera may not be fully initialized, and an error may occur.
|
||||
|
||||
the camera may not be fully initialized, and an error may occur.
|
||||
|
||||
Connect to the signal "image-captured" to be alerted when the picture
|
||||
is saved."""
|
||||
self.camerabin.set_property("location", filename)
|
||||
|
@ -178,18 +189,18 @@ class CameraMugshotDialog(CameraDialog):
|
|||
|
||||
# Get the message type.
|
||||
t = message.type
|
||||
|
||||
|
||||
# Initial load, wait until camera is ready before enabling capture.
|
||||
if t == Gst.MessageType.ASYNC_DONE:
|
||||
self.record_button.set_sensitive(True)
|
||||
|
||||
|
||||
if t == Gst.MessageType.ELEMENT:
|
||||
# Keep the camera working after several pictures are taken.
|
||||
if message.get_structure().get_name() == "image-captured":
|
||||
self.camerabin.set_state(Gst.Sate.NULL)
|
||||
self.camerabin.set_state(Gst.State.PLAYING)
|
||||
self.emit("image-captured", self.filename)
|
||||
|
||||
|
||||
# Enable interface elements once the images are finished saving.
|
||||
elif message.get_structure().get_name() == "image-done":
|
||||
self.apply_button.set_sensitive(True)
|
||||
|
@ -199,7 +210,7 @@ class CameraMugshotDialog(CameraDialog):
|
|||
# Stop the stream if the EOS (end of stream) message is received.
|
||||
if t == Gst.MessageType.EOS:
|
||||
self.camerabin.set_state(Gst.State.NULL)
|
||||
|
||||
|
||||
# Capture and report any error received.
|
||||
elif t == Gst.MessageType.ERROR:
|
||||
err, debug = message.parse_error()
|
||||
|
@ -223,48 +234,49 @@ class CameraMugshotDialog(CameraDialog):
|
|||
if message_name == "prepare-window-handle":
|
||||
imagesink = message.src
|
||||
imagesink.set_property("force-aspect-ratio", True)
|
||||
imagesink.set_window_handle(self.video_window.get_window().get_xid())
|
||||
imagesink.set_window_handle(
|
||||
self.video_window.get_window().get_xid())
|
||||
|
||||
def __on_video_window_realized(self, widget, data=None):
|
||||
"""Internal signal handler, used to set up the xid for the drawing area
|
||||
"""Internal signal handler, used to set up the xid for the drawing area
|
||||
in a thread safe manner. Do not call directly."""
|
||||
self._set_video_window_id()
|
||||
|
||||
def _set_video_window_id(self):
|
||||
"""Set the window ID only if not previously configured."""
|
||||
if not self.realized and self.video_window.get_window() is not None:
|
||||
x = self.video_window.get_window().get_xid()
|
||||
self.video_window.get_window().get_xid()
|
||||
self.realized = True
|
||||
|
||||
|
||||
def on_camera_record_clicked(self, widget):
|
||||
"""When the camera record/retry button is clicked:
|
||||
Record: Pause the video, start the capture, enable apply and retry.
|
||||
Retry: Restart the video stream."""
|
||||
# Remove any previous temporary file.
|
||||
if self.filename and os.path.isfile(self.filename):
|
||||
if self.filename and os.path.isfile(self.filename):
|
||||
os.remove(self.filename)
|
||||
|
||||
|
||||
# Retry action.
|
||||
if self.apply_button.get_sensitive():
|
||||
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
|
||||
self.apply_button.set_sensitive(False)
|
||||
self.play()
|
||||
|
||||
|
||||
# Record (Capture) action.
|
||||
else:
|
||||
# Create a new temporary file.
|
||||
tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
||||
tmpfile.close()
|
||||
self.filename = tmpfile.name
|
||||
|
||||
|
||||
# Capture the current image.
|
||||
self.take_picture(self.filename)
|
||||
|
||||
|
||||
# Set the record button to retry, and disable it until the capture
|
||||
# finishes.
|
||||
self.record_button.set_label(_("Retry"))
|
||||
self.record_button.set_sensitive(False)
|
||||
|
||||
|
||||
def on_camera_apply_clicked(self, widget):
|
||||
"""When the camera Apply button is clicked, crop the current photo and
|
||||
emit a signal to let the main application know there is a new file
|
||||
|
@ -272,7 +284,7 @@ class CameraMugshotDialog(CameraDialog):
|
|||
self.center_crop(self.filename)
|
||||
self.emit("apply", self.filename)
|
||||
self.hide()
|
||||
|
||||
|
||||
def on_camera_cancel_clicked(self, widget):
|
||||
"""When the Cancel button is clicked, just hide the dialog."""
|
||||
self.hide()
|
||||
|
@ -285,54 +297,56 @@ class CameraMugshotDialog(CameraDialog):
|
|||
os.remove(self.filename)
|
||||
# Clean up the camera before exiting
|
||||
self.camerabin.set_state(Gst.State.NULL)
|
||||
|
||||
|
||||
def on_camera_mugshot_dialog_hide(self, widget, data=None):
|
||||
"""When the dialog is hidden, pause the camera recording."""
|
||||
self.pause()
|
||||
|
||||
|
||||
def on_camera_mugshot_dialog_show(self, widget, data=None):
|
||||
"""When the dialog is shown, set the record button to record, disable
|
||||
"""When the dialog is shown, set the record button to record, disable
|
||||
the apply button, and start the camera."""
|
||||
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
|
||||
self.apply_button.set_sensitive(False)
|
||||
self.show_all()
|
||||
self.play()
|
||||
|
||||
|
||||
def center_crop(self, filename):
|
||||
"""Crop the specified file to square dimensions."""
|
||||
# Load the image into a Pixbuf.
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
|
||||
|
||||
|
||||
# Get the image dimensions.
|
||||
height = pixbuf.get_height()
|
||||
width = pixbuf.get_width()
|
||||
start_x = 0
|
||||
start_y = 0
|
||||
|
||||
|
||||
# Calculate a balanced center.
|
||||
if width > height:
|
||||
start_x = (width-height)/2
|
||||
start_x = (width - height) / 2
|
||||
width = height
|
||||
else:
|
||||
start_y = (height-width)/2
|
||||
start_y = (height - width) / 2
|
||||
height = width
|
||||
|
||||
|
||||
# Create a new cropped pixbuf.
|
||||
new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height)
|
||||
|
||||
|
||||
# Overwrite the temporary file with our new cropped image.
|
||||
new_pixbuf.savev(filename, "png", [], [])
|
||||
|
||||
|
||||
def on_camera_mugshot_dialog_delete_event(self, widget, data=None):
|
||||
"""Override the dialog delete event to just hide the window."""
|
||||
self.hide()
|
||||
return True
|
||||
|
||||
|
||||
# Signals used by CameraMugshotDialog:
|
||||
# image-captured: emitted when the camera is done capturing an image.
|
||||
# apply: emitted when the apply button has been pressed and there is a new file saved for use.
|
||||
__gsignals__ = {'image-captured' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'apply' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,))
|
||||
}
|
||||
|
||||
# apply: emitted when the apply button has been pressed and there is a
|
||||
# new file saved for use.
|
||||
__gsignals__ = {'image-captured': (GObject.SIGNAL_RUN_LAST,
|
||||
GObject.TYPE_NONE,
|
||||
(GObject.TYPE_PYOBJECT,)),
|
||||
'apply': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
|
||||
(GObject.TYPE_STRING,))
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -28,7 +28,7 @@ import dbus
|
|||
|
||||
import tempfile
|
||||
|
||||
from gi.repository import Gtk, Gdk, GdkPixbuf # pylint: disable=E0611
|
||||
from gi.repository import Gtk, GdkPixbuf # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot')
|
||||
|
||||
|
@ -44,52 +44,64 @@ libreoffice_prefs = os.path.join(home, '.config', 'libreoffice', '4', 'user',
|
|||
pidgin_prefs = os.path.join(home, '.purple', 'prefs.xml')
|
||||
faces_dir = '/usr/share/pixmaps/faces/'
|
||||
|
||||
|
||||
def which(command):
|
||||
'''Use the system command which to get the absolute path for the given
|
||||
command.'''
|
||||
command = subprocess.Popen(['which', command], \
|
||||
command = subprocess.Popen(['which', command],
|
||||
stdout=subprocess.PIPE).stdout.read().strip()
|
||||
if command == '':
|
||||
if command == '':
|
||||
logger.debug('Command "%s" could not be found.' % command)
|
||||
return None
|
||||
return command
|
||||
|
||||
|
||||
|
||||
def has_running_process(name):
|
||||
"""Check for a running process, return True if any listings are found."""
|
||||
command = 'ps -ef | grep " %s" | grep -v "grep" | wc -l' % name
|
||||
n = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||
n = subprocess.Popen(command, stdout=subprocess.PIPE,
|
||||
shell=True).stdout.read().strip()
|
||||
return int(n) > 0
|
||||
|
||||
|
||||
|
||||
def has_gstreamer_camerabin_support():
|
||||
"""Return True if gstreamer1.0 camerabin element is available."""
|
||||
process = subprocess.Popen(["gst-inspect-1.0", "camerabin"],
|
||||
stdout=subprocess.PIPE,
|
||||
process = subprocess.Popen(["gst-inspect-1.0", "camerabin"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
has_support = process.returncode == 0
|
||||
if not has_support:
|
||||
logger.debug('camerabin element unavailable. Do you have gstreamer1.0-plugins-good installed?')
|
||||
element = 'camerabin'
|
||||
plugin = 'gstreamer1.0-plugins-good'
|
||||
logger.debug('%s element unavailable. '
|
||||
'Do you have %s installed?' % (element, plugin))
|
||||
return has_support
|
||||
|
||||
|
||||
def has_gstreamer_camerasrc_support():
|
||||
"""Return True if gstreamer1.0 v4l2src element is available."""
|
||||
process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"],
|
||||
stdout=subprocess.PIPE,
|
||||
process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
has_support = process.returncode == 0
|
||||
if not has_support:
|
||||
logger.debug('v4l2src element unavailable. Do you have gstreamer1.0-plugins-good installed?')
|
||||
element = 'v4l2src'
|
||||
plugin = 'gstreamer1.0-plugins-good'
|
||||
logger.debug('%s element unavailable. '
|
||||
'Do you have %s installed?' % (element, plugin))
|
||||
return has_support
|
||||
|
||||
|
||||
|
||||
def get_camera_installed():
|
||||
"""Return True if /dev/video0 exists."""
|
||||
if not os.path.exists('/dev/video0'):
|
||||
logger.debug('Camera not detected at /dev/video0')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def get_has_camera_support():
|
||||
"""Return True if cameras are fully supported by this application."""
|
||||
if not get_camera_installed():
|
||||
|
@ -102,10 +114,12 @@ def get_has_camera_support():
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
def detach_cb(menu, widget):
|
||||
'''Detach a widget from its attached widget.'''
|
||||
menu.detach()
|
||||
|
||||
|
||||
|
||||
def get_entry_value(entry_widget):
|
||||
"""Get the value from one of the Mugshot entries."""
|
||||
# Get the text from an entry, changing none to ''
|
||||
|
@ -113,10 +127,11 @@ def get_entry_value(entry_widget):
|
|||
if value.lower() == 'none':
|
||||
value = ''
|
||||
return value
|
||||
|
||||
def get_confirmation_dialog(parent, primary_message, secondary_message,
|
||||
|
||||
|
||||
def get_confirmation_dialog(parent, primary_message, secondary_message,
|
||||
icon_name=None):
|
||||
"""Display a confirmation (yes/no) dialog configured with primary and
|
||||
"""Display a confirmation (yes/no) dialog configured with primary and
|
||||
secondary messages, as well as a custom icon if requested."""
|
||||
dialog = Gtk.MessageDialog(parent, flags=0, type=Gtk.MessageType.QUESTION,
|
||||
buttons=Gtk.ButtonsType.YES_NO,
|
||||
|
@ -129,7 +144,8 @@ def get_confirmation_dialog(parent, primary_message, secondary_message,
|
|||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
return response == Gtk.ResponseType.YES
|
||||
|
||||
|
||||
|
||||
def menu_position(self, menu, data=None, something_else=None):
|
||||
'''Position a menu at the bottom of its attached widget'''
|
||||
widget = menu.get_attach_widget()
|
||||
|
@ -141,15 +157,17 @@ def menu_position(self, menu, data=None, something_else=None):
|
|||
y = window_pos[1] + allocation.y + allocation.height
|
||||
return (x, y, True)
|
||||
|
||||
|
||||
# See mugshot_lib.Window.py for more details about how this class works
|
||||
class MugshotWindow(Window):
|
||||
"""Mugshot GtkWindow"""
|
||||
__gtype_name__ = "MugshotWindow"
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
|
||||
def finish_initializing(self, builder): # pylint: disable=E1002
|
||||
"""Set up the main window"""
|
||||
super(MugshotWindow, self).finish_initializing(builder)
|
||||
self.set_wmclass("Mugshot", "Mugshot")
|
||||
|
||||
|
||||
self.CameraDialog = CameraMugshotDialog
|
||||
|
||||
# User Image widgets
|
||||
|
@ -159,9 +177,9 @@ class MugshotWindow(Window):
|
|||
self.image_menu.attach_to_widget(self.image_button, detach_cb)
|
||||
self.image_from_camera = builder.get_object('image_from_camera')
|
||||
image_from_browse = builder.get_object('image_from_browse')
|
||||
image_from_browse.set_visible( os.path.exists(faces_dir) and \
|
||||
len(os.listdir(faces_dir)) > 0 )
|
||||
|
||||
image_from_browse.set_visible(os.path.exists(faces_dir) and
|
||||
len(os.listdir(faces_dir)) > 0)
|
||||
|
||||
# Entry widgets (chfn)
|
||||
self.first_name_entry = builder.get_object('first_name')
|
||||
self.last_name_entry = builder.get_object('last_name')
|
||||
|
@ -170,11 +188,11 @@ class MugshotWindow(Window):
|
|||
self.home_phone_entry = builder.get_object('home_phone')
|
||||
self.email_entry = builder.get_object('email')
|
||||
self.fax_entry = builder.get_object('fax')
|
||||
|
||||
|
||||
# Stock photo browser
|
||||
self.stock_browser = builder.get_object('stock_browser')
|
||||
self.iconview = builder.get_object('stock_iconview')
|
||||
|
||||
|
||||
# File Chooser Dialog
|
||||
self.chooser = builder.get_object('filechooserdialog')
|
||||
self.crop_center = builder.get_object('crop_center')
|
||||
|
@ -186,12 +204,12 @@ class MugshotWindow(Window):
|
|||
image_filter.set_name('Images')
|
||||
image_filter.add_mime_type('image/*')
|
||||
self.chooser.add_filter(image_filter)
|
||||
|
||||
|
||||
self.tmpfile = None
|
||||
|
||||
# Populate all of the widgets.
|
||||
self.init_user_details()
|
||||
|
||||
|
||||
def init_user_details(self):
|
||||
"""Initialize the user details entries and variables."""
|
||||
# Check for .face and set profile image.
|
||||
|
@ -202,7 +220,7 @@ class MugshotWindow(Window):
|
|||
else:
|
||||
self.set_user_image(None)
|
||||
self.updated_image = None
|
||||
|
||||
|
||||
# Search /etc/passwd for the current user's details.
|
||||
logger.debug('Getting user details from /etc/passwd')
|
||||
for line in open('/etc/passwd', 'r'):
|
||||
|
@ -211,7 +229,7 @@ class MugshotWindow(Window):
|
|||
details = line.split(':')[4]
|
||||
name, office, office_phone, home_phone = details.split(',', 3)
|
||||
break
|
||||
|
||||
|
||||
# Expand the user's fullname into first, last, and initials.
|
||||
try:
|
||||
first_name, last_name = name.split(' ', 1)
|
||||
|
@ -220,25 +238,27 @@ class MugshotWindow(Window):
|
|||
first_name = name
|
||||
last_name = ''
|
||||
initials = first_name[0]
|
||||
|
||||
|
||||
# If the variables are defined as 'none', use blank for cleanliness.
|
||||
if home_phone == 'none': home_phone = ''
|
||||
if office_phone == 'none': office_phone = ''
|
||||
|
||||
if home_phone == 'none':
|
||||
home_phone = ''
|
||||
if office_phone == 'none':
|
||||
office_phone = ''
|
||||
|
||||
# Get dconf settings
|
||||
logger.debug('Getting initials, email, and fax from dconf')
|
||||
if self.settings['initials'] != '':
|
||||
initials = self.settings['initials']
|
||||
email = self.settings['email']
|
||||
fax = self.settings['fax']
|
||||
|
||||
|
||||
# Set the class variables
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.initials = initials
|
||||
self.home_phone = home_phone
|
||||
self.office_phone = office_phone
|
||||
|
||||
|
||||
# Populate the GtkEntries.
|
||||
logger.debug('Populating entries')
|
||||
self.first_name_entry.set_text(self.first_name)
|
||||
|
@ -248,7 +268,7 @@ class MugshotWindow(Window):
|
|||
self.home_phone_entry.set_text(self.home_phone)
|
||||
self.email_entry.set_text(email)
|
||||
self.fax_entry.set_text(fax)
|
||||
|
||||
|
||||
# = Mugshot Window ======================================================= #
|
||||
def set_user_image(self, filename=None):
|
||||
"""Scale and set the user profile image."""
|
||||
|
@ -264,83 +284,84 @@ class MugshotWindow(Window):
|
|||
"""Allow only numbers and + in phone entry fields."""
|
||||
text = entry.get_text().strip()
|
||||
entry.set_text(''.join([i for i in text if i in '+0123456789']))
|
||||
|
||||
|
||||
def on_apply_button_clicked(self, widget):
|
||||
"""When the window Apply button is clicked, commit any relevant
|
||||
"""When the window Apply button is clicked, commit any relevant
|
||||
changes."""
|
||||
logger.debug('Applying changes...')
|
||||
if self.get_chfn_details_updated():
|
||||
returns = self.save_chfn_details()
|
||||
|
||||
self.save_chfn_details()
|
||||
|
||||
if self.get_libreoffice_details_updated():
|
||||
self.set_libreoffice_data()
|
||||
|
||||
|
||||
if self.updated_image:
|
||||
self.save_image()
|
||||
|
||||
|
||||
self.save_gsettings()
|
||||
self.destroy()
|
||||
|
||||
|
||||
def save_gsettings(self):
|
||||
"""Save details to dconf (the ones not tracked by /etc/passwd)"""
|
||||
logger.debug('Saving details to dconf: /apps/mugshot')
|
||||
self.settings.set_string('initials',
|
||||
self.settings.set_string('initials',
|
||||
get_entry_value(self.initials_entry))
|
||||
self.settings.set_string('email', get_entry_value(self.email_entry))
|
||||
self.settings.set_string('fax', get_entry_value(self.fax_entry))
|
||||
|
||||
|
||||
def entry_focus_next(self, widget):
|
||||
"""Focus the next available entry when pressing Enter."""
|
||||
logger.debug('Entry activated, focusing next widget.')
|
||||
vbox = widget.get_parent().get_parent().get_parent().get_parent()
|
||||
vbox.child_focus(Gtk.DirectionType.TAB_FORWARD)
|
||||
|
||||
|
||||
def on_cancel_button_clicked(self, widget):
|
||||
"""When the window cancel button is clicked, close the program."""
|
||||
logger.debug('Cancel clicked, goodbye.')
|
||||
self.destroy()
|
||||
|
||||
|
||||
# = Image Button and Menu ================================================ #
|
||||
def on_image_button_clicked(self, widget):
|
||||
"""When the menu button is clicked, display the photo menu."""
|
||||
if widget.get_active():
|
||||
logger.debug('Show photo menu')
|
||||
self.image_from_camera.set_visible(get_has_camera_support())
|
||||
self.image_menu.popup(None, None, menu_position,
|
||||
self.image_menu, 3,
|
||||
self.image_menu.popup(None, None, menu_position,
|
||||
self.image_menu, 3,
|
||||
Gtk.get_current_event_time())
|
||||
|
||||
|
||||
def on_image_menu_hide(self, widget):
|
||||
"""Untoggle the image button when the menu is hidden."""
|
||||
self.image_button.set_active(False)
|
||||
|
||||
|
||||
def on_camera_dialog_apply(self, widget, data=None):
|
||||
"""Commit changes when apply is clicked."""
|
||||
self.updated_image = data
|
||||
self.set_user_image(data)
|
||||
|
||||
|
||||
def save_image(self):
|
||||
"""Copy the updated image filename to ~/.face"""
|
||||
# Check if the image has been updated.
|
||||
if not self.updated_image:
|
||||
logger.debug('Photo not updated, not saving changes.')
|
||||
return False
|
||||
|
||||
|
||||
face = os.path.expanduser('~/.face')
|
||||
|
||||
|
||||
# If the .face file already exists, remove it first.
|
||||
logger.debug('Photo updated, saving changes.')
|
||||
if os.path.isfile(face):
|
||||
os.remove(face)
|
||||
|
||||
|
||||
# Copy the new file to ~/.face
|
||||
shutil.copyfile(self.updated_image, face)
|
||||
self.set_pidgin_buddyicon(face)
|
||||
self.updated_image = None
|
||||
return True
|
||||
|
||||
|
||||
def set_pidgin_buddyicon(self, filename=None):
|
||||
"""Sets the pidgin buddyicon to filename (usually ~/.face).
|
||||
|
||||
|
||||
If pidgin is running, use the dbus interface, otherwise directly modify
|
||||
the XML file."""
|
||||
if not os.path.exists(pidgin_prefs):
|
||||
|
@ -358,19 +379,19 @@ class MugshotWindow(Window):
|
|||
self.set_pidgin_buddyicon_xml(filename)
|
||||
else:
|
||||
logger.debug('Reject: Not updating pidgin buddy icon')
|
||||
|
||||
|
||||
def set_pidgin_buddyicon_dbus(self, filename=None):
|
||||
"""Set the pidgin buddy icon via dbus."""
|
||||
logger.debug('Updating pidgin buddy icon via dbus')
|
||||
bus = dbus.SessionBus()
|
||||
obj = bus.get_object("im.pidgin.purple.PurpleService",
|
||||
obj = bus.get_object("im.pidgin.purple.PurpleService",
|
||||
"/im/pidgin/purple/PurpleObject")
|
||||
purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
|
||||
# To make the change instantly visible, set the icon to none first.
|
||||
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', '')
|
||||
if filename:
|
||||
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', filename)
|
||||
|
||||
|
||||
def set_pidgin_buddyicon_xml(self, filename=None):
|
||||
"""Set the buddyicon used by pidgin to filename (via the xml file)."""
|
||||
# This is hacky, but a working implementation for now...
|
||||
|
@ -380,9 +401,9 @@ class MugshotWindow(Window):
|
|||
if os.path.isfile(prefs_file):
|
||||
for line in open(prefs_file):
|
||||
if '<pref name=\'buddyicon\'' in line:
|
||||
new = line.split('value=')[0]
|
||||
new = line.split('value=')[0]
|
||||
if filename:
|
||||
new = new + 'value=\'%s\'/>\n' % filename
|
||||
new = new + 'value=\'%s\'/>\n' % filename
|
||||
else:
|
||||
new = new + 'value=\'\'/>\n'
|
||||
tmp_buffer.append(new)
|
||||
|
@ -392,7 +413,7 @@ class MugshotWindow(Window):
|
|||
for line in tmp_buffer:
|
||||
write_prefs.write(line)
|
||||
write_prefs.close()
|
||||
|
||||
|
||||
# = chfn functions ============================================ #
|
||||
def get_chfn_details_updated(self):
|
||||
"""Return True if chfn-related details have been modified."""
|
||||
|
@ -405,25 +426,25 @@ class MugshotWindow(Window):
|
|||
return True
|
||||
logger.debug('chfn details have NOT been modified.')
|
||||
return False
|
||||
|
||||
|
||||
def save_chfn_details(self):
|
||||
"""Commit changes to chfn-related details. For full name, changes must
|
||||
be performed as root. Other changes are done with the user password.
|
||||
|
||||
|
||||
Return exit codes for 1) full name changes and 2) home/work phone
|
||||
changes.
|
||||
|
||||
|
||||
e.g. [0, 0] (both passed)"""
|
||||
return_codes = []
|
||||
|
||||
|
||||
# Get the user's password
|
||||
password = self.get_password()
|
||||
if not password:
|
||||
return return_codes
|
||||
|
||||
|
||||
username = os.getenv('USER')
|
||||
chfn = which('chfn')
|
||||
|
||||
|
||||
# Get each of the updated values.
|
||||
first_name = get_entry_value(self.first_name_entry)
|
||||
last_name = get_entry_value(self.last_name_entry)
|
||||
|
@ -435,7 +456,7 @@ class MugshotWindow(Window):
|
|||
home_phone = get_entry_value(self.home_phone_entry)
|
||||
if home_phone == '':
|
||||
home_phone = 'none'
|
||||
|
||||
|
||||
# Full name can only be modified by root. Try using sudo to modify.
|
||||
logger.debug('Attempting to set fullname with sudo chfn')
|
||||
child = pexpect.spawn('sudo %s %s' % (chfn, username))
|
||||
|
@ -449,9 +470,9 @@ class MugshotWindow(Window):
|
|||
child.sendline('')
|
||||
except pexpect.TIMEOUT:
|
||||
# Password was incorrect, or sudo rights not granted
|
||||
logger.debug('Timeout reached, password was incorrect or sudo ' \
|
||||
logger.debug('Timeout reached, password was incorrect or sudo '
|
||||
'rights not granted.')
|
||||
pass
|
||||
pass
|
||||
child.close()
|
||||
if child.exitstatus == 0:
|
||||
self.first_name = first_name
|
||||
|
@ -479,7 +500,7 @@ class MugshotWindow(Window):
|
|||
self.home_phone = home_phone
|
||||
return_codes.append(child.exitstatus)
|
||||
return return_codes
|
||||
|
||||
|
||||
# = LibreOffice ========================================================== #
|
||||
def get_libreoffice_details_updated(self):
|
||||
"""Return True if LibreOffice settings need to be updated."""
|
||||
|
@ -506,14 +527,14 @@ class MugshotWindow(Window):
|
|||
return True
|
||||
logger.debug('LibreOffice details do not need to be updated.')
|
||||
return False
|
||||
|
||||
|
||||
def get_libreoffice_data(self):
|
||||
"""Get each of the preferences from the LibreOffice registymodifications
|
||||
preferences file.
|
||||
|
||||
|
||||
Return a dict with the details."""
|
||||
prefs_file = libreoffice_prefs
|
||||
data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '',
|
||||
data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '',
|
||||
'home_phone': '', 'office_phone': '', 'fax': ''}
|
||||
if os.path.isfile(prefs_file):
|
||||
logger.debug('Getting settings from %s' % prefs_file)
|
||||
|
@ -545,7 +566,7 @@ class MugshotWindow(Window):
|
|||
else:
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
def set_libreoffice_data(self):
|
||||
"""Update the LibreOffice registymodifications preferences file."""
|
||||
prefs_file = libreoffice_prefs
|
||||
|
@ -622,40 +643,61 @@ class MugshotWindow(Window):
|
|||
open_prefs = open(prefs_file, 'w')
|
||||
for line in tmp_buffer:
|
||||
open_prefs.write(line)
|
||||
|
||||
|
||||
if not first_name_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="givenname" oor:op="fuse"><value>%s</value></prop></item>\n' % first_name
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="givenname" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % first_name
|
||||
open_prefs.write(string)
|
||||
if not last_name_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="sn" oor:op="fuse"><value>%s</value></prop></item>\n' % last_name
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="sn" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % last_name
|
||||
open_prefs.write(string)
|
||||
if not initials_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="initials" oor:op="fuse"><value>%s</value></prop></item>\n' % initials
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="initials" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % initials
|
||||
open_prefs.write(string)
|
||||
if not email_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="mail" oor:op="fuse"><value>%s</value></prop></item>\n' % email
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="mail" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % email
|
||||
open_prefs.write(string)
|
||||
if not home_phone_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="homephone" oor:op="fuse"><value>%s</value></prop></item>\n' % home_phone
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="homephone" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % home_phone
|
||||
open_prefs.write(string)
|
||||
if not office_phone_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="telephonenumber" oor:op="fuse"><value>%s</value></prop></item>\n' % office_phone
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="telephonenumber" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % office_phone
|
||||
open_prefs.write(string)
|
||||
if not fax_updated:
|
||||
string = '<item oor:path="/org.openoffice.UserProfile/Data"><prop oor:name="facsimiletelephonenumber" oor:op="fuse"><value>%s</value></prop></item>\n' % fax
|
||||
string = \
|
||||
'<item oor:path="/org.openoffice.UserProfile/Data">'
|
||||
'<prop oor:name="facsimiletelephonenumber" oor:op="fuse">'
|
||||
'<value>%s</value></prop></item>\n' % fax
|
||||
open_prefs.write(string)
|
||||
open_prefs.write('</oor:items>')
|
||||
open_prefs.close()
|
||||
else:
|
||||
logger.debug('Reject: Not updating.')
|
||||
|
||||
|
||||
# = Stock Browser ======================================================== #
|
||||
def on_image_from_stock_activate(self, widget):
|
||||
"""When the 'Select image from stock' menu item is clicked, load and
|
||||
"""When the 'Select image from stock' menu item is clicked, load and
|
||||
display the stock photo browser."""
|
||||
self.load_stock_browser()
|
||||
self.stock_browser.show_all()
|
||||
|
||||
|
||||
def load_stock_browser(self):
|
||||
"""Load the stock photo browser."""
|
||||
# Check if the photos have already been loaded.
|
||||
|
@ -663,7 +705,7 @@ class MugshotWindow(Window):
|
|||
if len(model) != 0:
|
||||
logger.debug("Stock browser already loaded.")
|
||||
return
|
||||
|
||||
|
||||
# If they have not, load each photo from /usr/share/pixmaps/faces.
|
||||
logger.debug("Loading stock browser photos.")
|
||||
for filename in os.listdir('/usr/share/pixmaps/faces'):
|
||||
|
@ -672,24 +714,24 @@ class MugshotWindow(Window):
|
|||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(full_path)
|
||||
scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER)
|
||||
model.append([full_path, scaled])
|
||||
|
||||
|
||||
def on_stock_iconview_selection_changed(self, widget):
|
||||
"""Enable stock submission only when an item is selected."""
|
||||
selected_items = self.iconview.get_selected_items()
|
||||
self.builder.get_object('stock_ok').set_sensitive(
|
||||
len(selected_items) > 0)
|
||||
|
||||
|
||||
def on_stock_browser_delete_event(self, widget, event):
|
||||
"""Hide the stock browser instead of deleting it."""
|
||||
widget.hide()
|
||||
return True
|
||||
|
||||
|
||||
def on_stock_cancel_clicked(self, widget):
|
||||
"""Hide the stock browser when Cancel is clicked."""
|
||||
self.stock_browser.hide()
|
||||
|
||||
|
||||
def on_stock_ok_clicked(self, widget):
|
||||
"""When the stock browser OK button is clicked, get the currently
|
||||
"""When the stock browser OK button is clicked, get the currently
|
||||
selected photo and set it to the user profile image."""
|
||||
selected_items = self.iconview.get_selected_items()
|
||||
if len(selected_items) != 0:
|
||||
|
@ -697,16 +739,16 @@ class MugshotWindow(Window):
|
|||
path = int(selected_items[0].to_string())
|
||||
filename = self.iconview.get_model()[path][0]
|
||||
logger.debug("Selected %s" % filename)
|
||||
|
||||
|
||||
# Update variables and widgets, then hide.
|
||||
self.set_user_image(filename)
|
||||
self.updated_image = filename
|
||||
self.stock_browser.hide()
|
||||
|
||||
|
||||
def on_stock_iconview_item_activated(self, widget, path):
|
||||
"""Allow selecting a stock photo with Enter."""
|
||||
self.on_stock_ok_clicked(widget)
|
||||
|
||||
|
||||
# = Image Browser ======================================================== #
|
||||
def on_image_from_browse_activate(self, widget):
|
||||
"""Browse for a user profile image."""
|
||||
|
@ -719,12 +761,14 @@ class MugshotWindow(Window):
|
|||
self.tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
||||
self.tmpfile.close()
|
||||
self.updated_image = self.tmpfile.name
|
||||
self.filechooser_preview_pixbuf.savev(self.updated_image, "png", [], [])
|
||||
self.filechooser_preview_pixbuf.savev(self.updated_image, "png",
|
||||
[], [])
|
||||
logger.debug("Selected %s" % self.updated_image)
|
||||
self.set_user_image(self.updated_image)
|
||||
self.chooser.hide()
|
||||
|
||||
|
||||
def on_filechooserdialog_update_preview(self, widget):
|
||||
"""Update the preview image used in the file chooser."""
|
||||
filename = widget.get_filename()
|
||||
if not filename:
|
||||
self.file_chooser_preview.set_from_icon_name('folder', 128)
|
||||
|
@ -733,47 +777,50 @@ class MugshotWindow(Window):
|
|||
self.file_chooser_preview.set_from_icon_name('folder', 128)
|
||||
return
|
||||
filechooser_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
|
||||
|
||||
|
||||
# Get the image dimensions.
|
||||
height = filechooser_pixbuf.get_height()
|
||||
width = filechooser_pixbuf.get_width()
|
||||
start_x = 0
|
||||
start_y = 0
|
||||
|
||||
|
||||
if self.crop_center.get_active():
|
||||
# Calculate a balanced center.
|
||||
if width > height:
|
||||
start_x = (width-height)/2
|
||||
start_x = (width - height) / 2
|
||||
width = height
|
||||
else:
|
||||
start_y = (height-width)/2
|
||||
start_y = (height - width) / 2
|
||||
height = width
|
||||
|
||||
|
||||
elif self.crop_left.get_active():
|
||||
start_x = 0
|
||||
if width > height:
|
||||
width = height
|
||||
else:
|
||||
start_y = (height-width)/2
|
||||
start_y = (height - width) / 2
|
||||
height = width
|
||||
elif self.crop_right.get_active():
|
||||
if width > height:
|
||||
start_x = width-height
|
||||
start_x = width - height
|
||||
width = height
|
||||
else:
|
||||
start_y = (height-width)/2
|
||||
start_y = (height - width) / 2
|
||||
height = width
|
||||
|
||||
|
||||
# Create a new cropped pixbuf.
|
||||
self.filechooser_preview_pixbuf = filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height)
|
||||
|
||||
scaled = self.filechooser_preview_pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
|
||||
self.filechooser_preview_pixbuf = \
|
||||
filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height)
|
||||
|
||||
scaled = self.filechooser_preview_pixbuf.scale_simple(128, 128,
|
||||
GdkPixbuf.InterpType.HYPER)
|
||||
self.file_chooser_preview.set_from_pixbuf(scaled)
|
||||
|
||||
|
||||
def on_crop_changed(self, widget, data=None):
|
||||
"""Update the preview image when crop style is modified."""
|
||||
if widget.get_active():
|
||||
self.on_filechooserdialog_update_preview(self.chooser)
|
||||
|
||||
|
||||
# = Password Entry ======================================================= #
|
||||
def get_password(self):
|
||||
"""Display a password dialog for authenticating to sudo and chfn."""
|
||||
|
@ -789,9 +836,8 @@ class MugshotWindow(Window):
|
|||
return pw
|
||||
logger.debug("Cancelled")
|
||||
return None
|
||||
|
||||
|
||||
def on_password_entry_changed(self, widget):
|
||||
"""Enable password submission only when password is not blank."""
|
||||
self.builder.get_object('password_ok').set_sensitive(
|
||||
len(widget.get_text()) > 0)
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
'''Enhances builder connections, provides object to access glade objects'''
|
||||
|
||||
from gi.repository import GObject, Gtk # pylint: disable=E0611
|
||||
from gi.repository import GObject, Gtk # pylint: disable=E0611
|
||||
|
||||
import inspect
|
||||
import functools
|
||||
|
@ -46,6 +46,7 @@ class Builder(Gtk.Builder):
|
|||
'''
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the builder."""
|
||||
Gtk.Builder.__init__(self)
|
||||
self.widgets = {}
|
||||
self.glade_handler_dict = {}
|
||||
|
@ -120,7 +121,7 @@ class Builder(Gtk.Builder):
|
|||
connection_dict = {}
|
||||
connection_dict.update(self.glade_handler_dict)
|
||||
connection_dict.update(callback_handler_dict)
|
||||
for item in connection_dict.items():
|
||||
for item in list(connection_dict.items()):
|
||||
if item[1] is None:
|
||||
# the handler is missing so reroute to default_handler
|
||||
handler = functools.partial(
|
||||
|
@ -166,17 +167,19 @@ class Builder(Gtk.Builder):
|
|||
class UiFactory():
|
||||
''' provides an object with attributes as glade widgets'''
|
||||
def __init__(self, widget_dict):
|
||||
"""Initialize the UiFactory."""
|
||||
self._widget_dict = widget_dict
|
||||
for (widget_name, widget) in widget_dict.items():
|
||||
for (widget_name, widget) in list(widget_dict.items()):
|
||||
setattr(self, widget_name, widget)
|
||||
|
||||
# Mangle any non-usable names (like with spaces or dashes)
|
||||
# into pythonic ones
|
||||
cannot_message = """cannot bind ui.%s, name already exists
|
||||
consider using a pythonic name instead of design name '%s'"""
|
||||
consider_message = """consider using a pythonic name instead of design name '%s'"""
|
||||
|
||||
for (widget_name, widget) in widget_dict.items():
|
||||
consider_message = """consider using a pythonic name instead of
|
||||
design name '%s'"""
|
||||
|
||||
for (widget_name, widget) in list(widget_dict.items()):
|
||||
pyname = make_pyname(widget_name)
|
||||
if pyname != widget_name:
|
||||
if hasattr(self, pyname):
|
||||
|
@ -187,7 +190,7 @@ class UiFactory():
|
|||
|
||||
def iterator():
|
||||
'''Support 'for o in self' '''
|
||||
return iter(widget_dict.values())
|
||||
return iter(list(widget_dict.values()))
|
||||
setattr(self, '__iter__', iterator)
|
||||
|
||||
def __getitem__(self, name):
|
||||
|
@ -208,10 +211,11 @@ def make_pyname(name):
|
|||
return pyname
|
||||
|
||||
|
||||
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
|
||||
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
|
||||
# need to reimplement inspect.getmembers. GObject introspection doesn't
|
||||
# play nice with it.
|
||||
def getmembers(obj, check):
|
||||
"""Reimplementation of getmembers"""
|
||||
members = []
|
||||
for k in dir(obj):
|
||||
try:
|
||||
|
@ -260,7 +264,7 @@ def auto_connect_by_name(callback_obj, builder):
|
|||
|
||||
callback_handler_dict = dict_from_callback_obj(callback_obj)
|
||||
|
||||
for item in builder.widgets.items():
|
||||
for item in list(builder.widgets.items()):
|
||||
(widget_name, widget) = item
|
||||
signal_ids = []
|
||||
try:
|
||||
|
@ -296,7 +300,7 @@ def do_connect(item, signal_name, handler_names,
|
|||
widget_name, widget = item
|
||||
|
||||
for handler_name in handler_names:
|
||||
target = handler_name in callback_handler_dict.keys()
|
||||
target = handler_name in list(callback_handler_dict.keys())
|
||||
connection = (widget_name, signal_name, handler_name)
|
||||
duplicate = connection in connections
|
||||
if target and not duplicate:
|
||||
|
@ -312,7 +316,7 @@ def log_unconnected_functions(callback_handler_dict, connections):
|
|||
|
||||
connected_functions = [x[2] for x in connections]
|
||||
|
||||
handler_names = callback_handler_dict.keys()
|
||||
handler_names = list(callback_handler_dict.keys())
|
||||
unconnected = [x for x in handler_names if x.startswith('on_')]
|
||||
|
||||
for handler_name in connected_functions:
|
||||
|
|
|
@ -1,34 +1,36 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot_lib')
|
||||
|
||||
from . helpers import get_builder, show_uri, get_help_uri
|
||||
|
||||
|
||||
class CameraDialog(Gtk.Dialog):
|
||||
"""Camera Dialog"""
|
||||
__gtype_name__ = "CameraDialog"
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
|
||||
Returns a fully instantiated PreferencesDialog object.
|
||||
"""
|
||||
builder = get_builder('CameraMugshotDialog')
|
||||
|
@ -43,7 +45,7 @@ class CameraDialog(Gtk.Dialog):
|
|||
and creating a PreferencesDialog object with it in order to
|
||||
finish initializing the start of the new PerferencesMugshotDialog
|
||||
instance.
|
||||
|
||||
|
||||
Put your initialization code in here and leave __init__ undefined.
|
||||
"""
|
||||
|
||||
|
@ -54,8 +56,9 @@ class CameraDialog(Gtk.Dialog):
|
|||
# code for other initialization actions should be added here
|
||||
|
||||
def on_btn_close_clicked(self, widget, data=None):
|
||||
"""Destroy the dialog when closed."""
|
||||
self.destroy()
|
||||
|
||||
def on_btn_help_clicked(self, widget, data=None):
|
||||
"""Show the help dialog when Help is clicked."""
|
||||
show_uri(self, "ghelp:%s" % get_help_uri('preferences'))
|
||||
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
### DO NOT EDIT THIS FILE ###
|
||||
|
||||
from gi.repository import Gio, Gtk # pylint: disable=E0611
|
||||
from gi.repository import Gio, Gtk # pylint: disable=E0611
|
||||
import logging
|
||||
logger = logging.getLogger('mugshot_lib')
|
||||
|
||||
|
@ -24,12 +24,13 @@ import os
|
|||
|
||||
from . helpers import get_builder, show_uri, get_help_uri
|
||||
|
||||
# This class is meant to be subclassed by MugshotWindow. It provides
|
||||
# common functions and some boilerplate.
|
||||
|
||||
class Window(Gtk.Window):
|
||||
"""This class is meant to be subclassed by MugshotWindow. It provides
|
||||
common functions and some boilerplate."""
|
||||
__gtype_name__ = "Window"
|
||||
|
||||
# To construct a new instance of this method, the following notable
|
||||
# To construct a new instance of this method, the following notable
|
||||
# methods are called in this order:
|
||||
# __new__(cls)
|
||||
# __init__(self)
|
||||
|
@ -38,11 +39,11 @@ class Window(Gtk.Window):
|
|||
#
|
||||
# For this reason, it's recommended you leave __init__ empty and put
|
||||
# your initialization code in finish_initializing
|
||||
|
||||
|
||||
def __new__(cls):
|
||||
"""Special static method that's automatically called by Python when
|
||||
"""Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
|
||||
Returns a fully instantiated BaseMugshotWindow object.
|
||||
"""
|
||||
builder = get_builder('MugshotWindow')
|
||||
|
@ -60,15 +61,16 @@ class Window(Gtk.Window):
|
|||
# Get a reference to the builder and set up the signals.
|
||||
self.builder = builder
|
||||
self.ui = builder.get_ui(self, True)
|
||||
self.CameraDialog = None # class
|
||||
self.camera_dialog = None # instance
|
||||
self.CameraDialog = None # class
|
||||
self.camera_dialog = None # instance
|
||||
|
||||
self.settings = Gio.Settings("apps.mugshot")
|
||||
self.settings.connect('changed', self.on_preferences_changed)
|
||||
|
||||
def on_help_activate(self, widget, data=None):
|
||||
"""Show the Help documentation when Help is clicked."""
|
||||
show_uri(self, "ghelp:%s" % get_help_uri())
|
||||
|
||||
|
||||
def on_menu_camera_activate(self, widget, data=None):
|
||||
"""Display the camera window for mugshot."""
|
||||
if self.camera_dialog is not None:
|
||||
|
@ -76,8 +78,7 @@ class Window(Gtk.Window):
|
|||
self.camera_dialog.show()
|
||||
elif self.CameraDialog is not None:
|
||||
logger.debug('create new camera_dialog')
|
||||
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
|
||||
#self.camera_dialog.connect('destroy', self.on_camera_dialog_destroyed)
|
||||
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
|
||||
self.camera_dialog.connect('apply', self.on_camera_dialog_apply)
|
||||
self.camera_dialog.show()
|
||||
|
||||
|
@ -89,5 +90,6 @@ class Window(Gtk.Window):
|
|||
Gtk.main_quit()
|
||||
|
||||
def on_preferences_changed(self, settings, key, data=None):
|
||||
logger.debug('preference changed: %s = %s' % (key, str(settings.get_value(key))))
|
||||
|
||||
"""Log preference updates."""
|
||||
logger.debug('preference changed: %s = %s' %
|
||||
(key, str(settings.get_value(key))))
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -23,12 +23,11 @@ import os
|
|||
from . mugshotconfig import get_data_file
|
||||
from . Builder import Builder
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
def get_builder(builder_file_name):
|
||||
"""Return a fully-instantiated Gtk.Builder instance from specified ui
|
||||
"""Return a fully-instantiated Gtk.Builder instance from specified ui
|
||||
file
|
||||
|
||||
|
||||
:param builder_file_name: The name of the builder file, without extension.
|
||||
Assumed to be in the 'ui' directory under the data path.
|
||||
"""
|
||||
|
@ -43,25 +42,31 @@ def get_builder(builder_file_name):
|
|||
return builder
|
||||
|
||||
|
||||
# Owais Lone : To get quick access to icons and stuff.
|
||||
def get_media_file(media_file_name):
|
||||
"""Retrieve the filename for the specified file."""
|
||||
media_filename = get_data_file('media', '%s' % (media_file_name,))
|
||||
if not os.path.exists(media_filename):
|
||||
media_filename = None
|
||||
|
||||
return "file:///"+media_filename
|
||||
return "file:///" + media_filename
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
"""Handle NULL"""
|
||||
def emit(self, record):
|
||||
"""Do not emit anything."""
|
||||
pass
|
||||
|
||||
|
||||
def set_up_logging(opts):
|
||||
"""Set up the logging formatter."""
|
||||
# add a handler to prevent basicConfig
|
||||
root = logging.getLogger()
|
||||
null_handler = NullHandler()
|
||||
root.addHandler(null_handler)
|
||||
|
||||
formatter = logging.Formatter("%(levelname)s:%(name)s: %(funcName)s() '%(message)s'")
|
||||
formatter = logging.Formatter("%(levelname)s:%(name)s:"
|
||||
" %(funcName)s() '%(message)s'")
|
||||
|
||||
logger = logging.getLogger('mugshot')
|
||||
logger_sh = logging.StreamHandler()
|
||||
|
@ -80,7 +85,9 @@ def set_up_logging(opts):
|
|||
if opts.verbose > 1:
|
||||
lib_logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def get_help_uri(page=None):
|
||||
"""Get the URI to be used for Help."""
|
||||
# help_uri from source tree - default language
|
||||
here = os.path.dirname(__file__)
|
||||
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
|
||||
|
@ -95,11 +102,14 @@ def get_help_uri(page=None):
|
|||
|
||||
return help_uri
|
||||
|
||||
|
||||
def show_uri(parent, link):
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
"""Open the URI."""
|
||||
from gi.repository import Gtk # pylint: disable=E0611
|
||||
screen = parent.get_screen()
|
||||
Gtk.show_uri(screen, link, Gtk.get_current_event_time())
|
||||
|
||||
|
||||
def alias(alternative_function_name):
|
||||
'''see http://www.drdobbs.com/web-development/184406073#l9'''
|
||||
def decorator(function):
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
|
||||
### BEGIN LICENSE
|
||||
# Copyright (C) 2013 Sean Davis <smd.seandavis@gmail.com>
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3, as published
|
||||
# by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranties of
|
||||
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
|
||||
# PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
### END LICENSE
|
||||
|
||||
|
@ -30,7 +30,6 @@ __version__ = '0.1'
|
|||
|
||||
import os
|
||||
|
||||
from locale import gettext as _
|
||||
|
||||
class project_path_not_found(Exception):
|
||||
"""Raised when we can't find the project directory."""
|
||||
|
@ -66,4 +65,5 @@ def get_data_path():
|
|||
|
||||
|
||||
def get_version():
|
||||
"""Return the program version."""
|
||||
return __version__
|
||||
|
|
Loading…
Reference in New Issue