From 3d16afe6bb18a78a200eb206f14720c329b90626 Mon Sep 17 00:00:00 2001 From: Sean Davis Date: Fri, 12 Jul 2013 13:56:24 -0400 Subject: [PATCH] Initial project creation with Quickly! --- .quickly | 3 + AUTHORS | 1 + bin/mugshot | 36 ++ .../schemas/net.launchpad.mugshot.gschema.xml | 10 + data/media/background.png | Bin 0 -> 8759 bytes data/media/mugshot.svg | 74 +++++ data/ui/AboutMugshotDialog.ui | 38 +++ data/ui/MugshotWindow.ui | 298 +++++++++++++++++ data/ui/PreferencesMugshotDialog.ui | 112 +++++++ data/ui/about_mugshot_dialog.xml | 9 + data/ui/mugshot_window.xml | 8 + data/ui/preferences_mugshot_dialog.xml | 9 + help/C/figures/icon.png | Bin 0 -> 669 bytes help/C/index.page | 44 +++ help/C/preferences.page | 18 + help/C/topic1.page | 18 + mugshot.desktop.in | 8 + mugshot/AboutMugshotDialog.py | 22 ++ mugshot/MugshotWindow.py | 28 ++ mugshot/PreferencesMugshotDialog.py | 33 ++ mugshot/__init__.py | 33 ++ mugshot_lib/AboutDialog.py | 39 +++ mugshot_lib/Builder.py | 314 ++++++++++++++++++ mugshot_lib/PreferencesDialog.py | 53 +++ mugshot_lib/Window.py | 118 +++++++ mugshot_lib/__init__.py | 14 + mugshot_lib/helpers.py | 100 ++++++ mugshot_lib/mugshotconfig.py | 58 ++++ setup.py | 137 ++++++++ tests/test_example.py | 26 ++ tests/test_lint.py | 30 ++ 31 files changed, 1691 insertions(+) create mode 100644 .quickly create mode 100644 AUTHORS create mode 100755 bin/mugshot create mode 100644 data/glib-2.0/schemas/net.launchpad.mugshot.gschema.xml create mode 100644 data/media/background.png create mode 100644 data/media/mugshot.svg create mode 100644 data/ui/AboutMugshotDialog.ui create mode 100644 data/ui/MugshotWindow.ui create mode 100644 data/ui/PreferencesMugshotDialog.ui create mode 100644 data/ui/about_mugshot_dialog.xml create mode 100644 data/ui/mugshot_window.xml create mode 100644 data/ui/preferences_mugshot_dialog.xml create mode 100644 help/C/figures/icon.png create mode 100644 help/C/index.page create mode 100644 help/C/preferences.page create mode 100644 help/C/topic1.page create mode 100644 mugshot.desktop.in create mode 100644 mugshot/AboutMugshotDialog.py create mode 100644 mugshot/MugshotWindow.py create mode 100644 mugshot/PreferencesMugshotDialog.py create mode 100644 mugshot/__init__.py create mode 100644 mugshot_lib/AboutDialog.py create mode 100644 mugshot_lib/Builder.py create mode 100644 mugshot_lib/PreferencesDialog.py create mode 100644 mugshot_lib/Window.py create mode 100644 mugshot_lib/__init__.py create mode 100644 mugshot_lib/helpers.py create mode 100644 mugshot_lib/mugshotconfig.py create mode 100644 setup.py create mode 100644 tests/test_example.py create mode 100644 tests/test_lint.py diff --git a/.quickly b/.quickly new file mode 100644 index 0000000..338c839 --- /dev/null +++ b/.quickly @@ -0,0 +1,3 @@ +project = mugshot +version = 12.08.1 +template = ubuntu-application diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..7b062b0 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Copyright (C) YYYY diff --git a/bin/mugshot b/bin/mugshot new file mode 100755 index 0000000..e5646f6 --- /dev/null +++ b/bin/mugshot @@ -0,0 +1,36 @@ +#!/usr/bin/python +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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() diff --git a/data/glib-2.0/schemas/net.launchpad.mugshot.gschema.xml b/data/glib-2.0/schemas/net.launchpad.mugshot.gschema.xml new file mode 100644 index 0000000..285bcb0 --- /dev/null +++ b/data/glib-2.0/schemas/net.launchpad.mugshot.gschema.xml @@ -0,0 +1,10 @@ + + + + + '' + Sample setting + Longer description of this sample setting. Talk about allowed values and what it does. + + + diff --git a/data/media/background.png b/data/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd54d7c37a5cf863b37b13a2066b805d27e02ca GIT binary patch literal 8759 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 literal 0 HcmV?d00001 diff --git a/data/media/mugshot.svg b/data/media/mugshot.svg new file mode 100644 index 0000000..5f39371 --- /dev/null +++ b/data/media/mugshot.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/data/ui/AboutMugshotDialog.ui b/data/ui/AboutMugshotDialog.ui new file mode 100644 index 0000000..d168d5d --- /dev/null +++ b/data/ui/AboutMugshotDialog.ui @@ -0,0 +1,38 @@ + + + + + + False + 5 + ../media/mugshot.svg + normal + Mugshot + + ../media/mugshot.svg + + + True + False + vertical + 2 + + + True + False + end + + + False + True + end + 0 + + + + + + + + + diff --git a/data/ui/MugshotWindow.ui b/data/ui/MugshotWindow.ui new file mode 100644 index 0000000..ba8894a --- /dev/null +++ b/data/ui/MugshotWindow.ui @@ -0,0 +1,298 @@ + + + + + + + True + False + gtk-help + + + False + Mugshot + ../media/mugshot.svg + + + True + False + 5 + + + True + False + + + True + False + False + _File + True + + + True + False + + + gtk-new + True + False + False + True + True + + + + + + gtk-open + True + False + False + True + True + + + + + + True + False + + + + + gtk-save + True + False + False + True + True + + + + + + gtk-save-as + True + False + False + True + True + + + + + + True + False + + + + + gtk-close + True + False + False + True + True + + + + + + + + + + True + False + False + _Edit + True + + + True + False + + + gtk-cut + True + False + False + True + True + + + + + + gtk-copy + True + False + False + True + True + + + + + + gtk-paste + True + False + False + True + True + + + + + + gtk-delete + True + False + False + True + True + + + + + + True + False + + + + + gtk-preferences + True + False + False + True + True + + + + + + + + + True + False + False + _View + True + + + + + True + False + False + _Help + True + + + True + False + + + Contents + True + False + False + image2 + False + + + + + + gtk-about + True + False + False + True + True + + + + + + + + + False + True + 0 + + + + + True + False + 30 + 5 + Your application has been created! + +To start changing this user interface, run 'quickly design', which will open Glade so you can edit the default windows and dialogs. + +To change the behavior and edit the python code, run 'quickly edit', which will bring up a text editor. + True + + + True + True + 1 + + + + + True + False + 5 + 5 + ../media/background.png + + + True + True + 15 + 2 + + + + + True + False + 2 + + + True + False + 0 + 5 + 5 + Status Area + + + True + True + 0 + + + + + False + True + end + 3 + + + + + + diff --git a/data/ui/PreferencesMugshotDialog.ui b/data/ui/PreferencesMugshotDialog.ui new file mode 100644 index 0000000..aedc25c --- /dev/null +++ b/data/ui/PreferencesMugshotDialog.ui @@ -0,0 +1,112 @@ + + + + + + False + Mugshot Preferences + ../media/mugshot.svg + normal + + + True + False + vertical + 12 + + + True + False + end + + + gtk-help + True + True + True + False + True + + + False + False + 0 + True + + + + + gtk-close + True + True + True + False + True + + + False + False + 1 + + + + + False + True + end + 0 + + + + + True + False + 6 + 6 + 1 + 2 + + + True + True + True + + + + 1 + 0 + 1 + 1 + + + + + True + False + 0 + _Example entry: + True + example_entry + + + 0 + 0 + 1 + 1 + + + + + False + True + 1 + + + + + + btn_help + btn_close + + + diff --git a/data/ui/about_mugshot_dialog.xml b/data/ui/about_mugshot_dialog.xml new file mode 100644 index 0000000..fd40754 --- /dev/null +++ b/data/ui/about_mugshot_dialog.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/data/ui/mugshot_window.xml b/data/ui/mugshot_window.xml new file mode 100644 index 0000000..c11d3b3 --- /dev/null +++ b/data/ui/mugshot_window.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/data/ui/preferences_mugshot_dialog.xml b/data/ui/preferences_mugshot_dialog.xml new file mode 100644 index 0000000..4f04236 --- /dev/null +++ b/data/ui/preferences_mugshot_dialog.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/help/C/figures/icon.png b/help/C/figures/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e5cf5ffeaa2f7b06e0e885786e63a7e534cd588c GIT binary patch literal 669 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47<~hLLR|j?0RzMTP=^1xD*qc4 z-sY-&D^mVcr1Ysw@pG}<=R CCZUm-`98lzP|tW?c=|n zU;qF5{{Q#S|9^h}|NH0vzrVnMY&bMyDlmNYN`m}?85o(_IrwDN)U~vA(o4%KD(hO> z+B>>>7p~g6W8cAJ=gwcceC77Nm#^Nw`}E`YA48L6l|Y>rJzX3_DsJhX4OezbWN81m zf0MIP^f#~N&!4uOIHxwd=gr)Yb^pER9)AC6XXTTXT_wlclh#iCCCs7z!7`U|>Xgz) zv-ia;(bmx9nelb=pDx=*v6a^vcOIU7{?X+^wn>VnjXP_7rOOvru)CNxL~gy!U|lB1 zX1eRcrth|l290joocPYj ziM+Y)Y{#4|`f1;cHy3~2IxlMC#Z~s8Sbtik{`BQ`o%(^x>#y=|{5WynGpVV6n11r> VO6S+j*#q<(gQu&X%Q~loCIA>~ZO;Gz literal 0 HcmV?d00001 diff --git a/help/C/index.page b/help/C/index.page new file mode 100644 index 0000000..e85f0c7 --- /dev/null +++ b/help/C/index.page @@ -0,0 +1,44 @@ + + + + + Mugshot + The Mugshot help. + + Your Name + Your E-mail + 2010 + + +

Creative Commons Attribution-Share Alike 3.0 Unported License

+
+
+ + +<!-- This shows on the page in title font --> +<!-- the icon only shows when installed --> +<media type="image" mime="image/png" src="figures/icon.png">[icon]</media> +<app>Mugshot</app> Help + + +

This is an example guide page. It's main function is to link together the help topics.

+ + +
+ +Contents +
+ + + +

+ Your script or application looks better if it has help files similar to other applications in ubuntu. +

+

+ Some people think that help files are only for apps that are difficult to use. +

+
+ +
diff --git a/help/C/preferences.page b/help/C/preferences.page new file mode 100644 index 0000000..67d6a70 --- /dev/null +++ b/help/C/preferences.page @@ -0,0 +1,18 @@ + + + + + + Your Name + Your E-mail + 2010 + + Optional short description of Preferences for contents page + + +Preferences +

This is the preferences page.

+ +
diff --git a/help/C/topic1.page b/help/C/topic1.page new file mode 100644 index 0000000..1f21e94 --- /dev/null +++ b/help/C/topic1.page @@ -0,0 +1,18 @@ + + + + + + Your Name + Your E-mail + 2010 + + Optional short description of Topic 1 for contents page + + +Topic 1 +

This is an example topic page. It's main function is to describe one topic.

+ +
diff --git a/mugshot.desktop.in b/mugshot.desktop.in new file mode 100644 index 0000000..46fbe95 --- /dev/null +++ b/mugshot.desktop.in @@ -0,0 +1,8 @@ +[Desktop Entry] +_Name=Mugshot +_Comment=Mugshot application +Categories=GNOME;Utility; +Exec=mugshot +Icon=mugshot +Terminal=false +Type=Application diff --git a/mugshot/AboutMugshotDialog.py b/mugshot/AboutMugshotDialog.py new file mode 100644 index 0000000..5653f92 --- /dev/null +++ b/mugshot/AboutMugshotDialog.py @@ -0,0 +1,22 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +from locale import gettext as _ + +import logging +logger = logging.getLogger('mugshot') + +from mugshot_lib.AboutDialog import AboutDialog + +# See mugshot_lib.AboutDialog.py for more details about how this class works. +class AboutMugshotDialog(AboutDialog): + __gtype_name__ = "AboutMugshotDialog" + + def finish_initializing(self, builder): # pylint: disable=E1002 + """Set up the about dialog""" + super(AboutMugshotDialog, self).finish_initializing(builder) + + # Code for other initialization actions should be added here. + diff --git a/mugshot/MugshotWindow.py b/mugshot/MugshotWindow.py new file mode 100644 index 0000000..98a5f3a --- /dev/null +++ b/mugshot/MugshotWindow.py @@ -0,0 +1,28 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +from locale import gettext as _ + +from gi.repository import Gtk # pylint: disable=E0611 +import logging +logger = logging.getLogger('mugshot') + +from mugshot_lib import Window +from mugshot.AboutMugshotDialog import AboutMugshotDialog +from mugshot.PreferencesMugshotDialog import PreferencesMugshotDialog + +# 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.AboutDialog = AboutMugshotDialog + self.PreferencesDialog = PreferencesMugshotDialog + + # Code for other initialization actions should be added here. + diff --git a/mugshot/PreferencesMugshotDialog.py b/mugshot/PreferencesMugshotDialog.py new file mode 100644 index 0000000..93e2559 --- /dev/null +++ b/mugshot/PreferencesMugshotDialog.py @@ -0,0 +1,33 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +# This is your preferences dialog. +# +# Define your preferences in +# data/glib-2.0/schemas/net.launchpad.mugshot.gschema.xml +# See http://developer.gnome.org/gio/stable/GSettings.html for more info. + +from gi.repository import Gio # pylint: disable=E0611 + +from locale import gettext as _ + +import logging +logger = logging.getLogger('mugshot') + +from mugshot_lib.PreferencesDialog import PreferencesDialog + +class PreferencesMugshotDialog(PreferencesDialog): + __gtype_name__ = "PreferencesMugshotDialog" + + def finish_initializing(self, builder): # pylint: disable=E1002 + """Set up the preferences dialog""" + super(PreferencesMugshotDialog, self).finish_initializing(builder) + + # Bind each preference widget to gsettings + settings = Gio.Settings("net.launchpad.mugshot") + widget = self.builder.get_object('example_entry') + settings.bind("example", widget, "text", Gio.SettingsBindFlags.DEFAULT) + + # Code for other initialization actions should be added here. diff --git a/mugshot/__init__.py b/mugshot/__init__.py new file mode 100644 index 0000000..24013e2 --- /dev/null +++ b/mugshot/__init__.py @@ -0,0 +1,33 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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() diff --git a/mugshot_lib/AboutDialog.py b/mugshot_lib/AboutDialog.py new file mode 100644 index 0000000..cdd8646 --- /dev/null +++ b/mugshot_lib/AboutDialog.py @@ -0,0 +1,39 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +### DO NOT EDIT THIS FILE ### + +from gi.repository import Gtk # pylint: disable=E0611 + +from . helpers import get_builder + +class AboutDialog(Gtk.AboutDialog): + __gtype_name__ = "AboutDialog" + + def __new__(cls): + """Special static method that's automatically called by Python when + constructing a new instance of this class. + + Returns a fully instantiated AboutDialog object. + """ + builder = get_builder('AboutMugshotDialog') + new_object = builder.get_object("about_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 AboutDialog object with it in order + to finish initializing the start of the new AboutMugshotDialog + 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) + diff --git a/mugshot_lib/Builder.py b/mugshot_lib/Builder.py new file mode 100644 index 0000000..87ace17 --- /dev/null +++ b/mugshot_lib/Builder.py @@ -0,0 +1,314 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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__ and connects them + + i.e. find widget,signal pair in builder and call + widget.connect(signal, on__)''' + + 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) diff --git a/mugshot_lib/PreferencesDialog.py b/mugshot_lib/PreferencesDialog.py new file mode 100644 index 0000000..9eedb7f --- /dev/null +++ b/mugshot_lib/PreferencesDialog.py @@ -0,0 +1,53 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +### DO NOT EDIT THIS FILE ### + +"""this dialog adjusts values in gsettings +""" + +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 PreferencesDialog(Gtk.Dialog): + __gtype_name__ = "PreferencesDialog" + + 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('PreferencesMugshotDialog') + new_object = builder.get_object("preferences_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')) + diff --git a/mugshot_lib/Window.py b/mugshot_lib/Window.py new file mode 100644 index 0000000..46f9bd9 --- /dev/null +++ b/mugshot_lib/Window.py @@ -0,0 +1,118 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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.PreferencesDialog = None # class + self.preferences_dialog = None # instance + self.AboutDialog = None # class + + self.settings = Gio.Settings("net.launchpad.mugshot") + self.settings.connect('changed', self.on_preferences_changed) + + # Optional application indicator support + # Run 'quickly add indicator' to get started. + # More information: + # http://owaislone.org/quickly-add-indicator/ + # https://wiki.ubuntu.com/DesktopExperienceTeam/ApplicationIndicators + try: + from mugshot import indicator + # self is passed so methods of this class can be called from indicator.py + # Comment this next line out to disable appindicator + self.indicator = indicator.new_application_indicator(self) + except ImportError: + pass + + def on_mnu_contents_activate(self, widget, data=None): + show_uri(self, "ghelp:%s" % get_help_uri()) + + def on_mnu_about_activate(self, widget, data=None): + """Display the about box for mugshot.""" + if self.AboutDialog is not None: + about = self.AboutDialog() # pylint: disable=E1102 + response = about.run() + about.destroy() + + def on_mnu_preferences_activate(self, widget, data=None): + """Display the preferences window for mugshot.""" + + """ From the PyGTK Reference manual + Say for example the preferences dialog is currently open, + and the user chooses Preferences from the menu a second time; + use the present() method to move the already-open dialog + where the user can see it.""" + if self.preferences_dialog is not None: + logger.debug('show existing preferences_dialog') + self.preferences_dialog.present() + elif self.PreferencesDialog is not None: + logger.debug('create new preferences_dialog') + self.preferences_dialog = self.PreferencesDialog() # pylint: disable=E1102 + self.preferences_dialog.connect('destroy', self.on_preferences_dialog_destroyed) + self.preferences_dialog.show() + # destroy command moved into dialog to allow for a help button + + def on_mnu_close_activate(self, widget, data=None): + """Signal handler for closing the MugshotWindow.""" + self.destroy() + + 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)))) + + def on_preferences_dialog_destroyed(self, widget, data=None): + '''only affects gui + + logically there is no difference between the user closing, + minimising or ignoring the preferences dialog''' + logger.debug('on_preferences_dialog_destroyed') + # to determine whether to create or present preferences_dialog + self.preferences_dialog = None + diff --git a/mugshot_lib/__init__.py b/mugshot_lib/__init__.py new file mode 100644 index 0000000..00a6cea --- /dev/null +++ b/mugshot_lib/__init__.py @@ -0,0 +1,14 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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 + diff --git a/mugshot_lib/helpers.py b/mugshot_lib/helpers.py new file mode 100644 index 0000000..34d1259 --- /dev/null +++ b/mugshot_lib/helpers.py @@ -0,0 +1,100 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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 diff --git a/mugshot_lib/mugshotconfig.py b/mugshot_lib/mugshotconfig.py new file mode 100644 index 0000000..9f47761 --- /dev/null +++ b/mugshot_lib/mugshotconfig.py @@ -0,0 +1,58 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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__ = '' +__version__ = 'VERSION' + +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 /../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__ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e2f4b22 --- /dev/null +++ b/setup.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### END LICENSE + +###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ###################### + +import os +import sys + +try: + import DistUtilsExtra.auto +except ImportError: + print >> sys.stderr, 'To build mugshot you need https://launchpad.net/python-distutils-extra' + sys.exit(1) +assert DistUtilsExtra.auto.__version__ >= '2.18', 'needs DistUtilsExtra.auto >= 2.18' + +def update_config(libdir, values = {}): + + filename = os.path.join(libdir, 'mugshot_lib/mugshotconfig.py') + oldvalues = {} + try: + fin = file(filename, 'r') + fout = file(filename + '.new', 'w') + + for line in fin: + fields = line.split(' = ') # Separate variable from value + if fields[0] in values: + oldvalues[fields[0]] = fields[1].strip() + line = "%s = %s\n" % (fields[0], values[fields[0]]) + fout.write(line) + + fout.flush() + fout.close() + fin.close() + os.rename(fout.name, fin.name) + except (OSError, IOError), e: + print ("ERROR: Can't find %s" % filename) + sys.exit(1) + return oldvalues + + +def move_desktop_file(root, target_data, prefix): + # The desktop file is rightly installed into install_data. But it should + # always really be installed into prefix, because while we can install + # normal data files anywhere we want, the desktop file needs to exist in + # the main system to be found. Only actually useful for /opt installs. + + old_desktop_path = os.path.normpath(root + target_data + + '/share/applications') + old_desktop_file = old_desktop_path + '/mugshot.desktop' + desktop_path = os.path.normpath(root + prefix + '/share/applications') + desktop_file = desktop_path + '/mugshot.desktop' + + if not os.path.exists(old_desktop_file): + print ("ERROR: Can't find", old_desktop_file) + sys.exit(1) + elif target_data != prefix + '/': + # This is an /opt install, so rename desktop file to use extras- + desktop_file = desktop_path + '/extras-mugshot.desktop' + try: + os.makedirs(desktop_path) + os.rename(old_desktop_file, desktop_file) + os.rmdir(old_desktop_path) + except OSError as e: + print ("ERROR: Can't rename", old_desktop_file, ":", e) + sys.exit(1) + + return desktop_file + +def update_desktop_file(filename, target_pkgdata, target_scripts): + + try: + fin = file(filename, 'r') + fout = file(filename + '.new', 'w') + + for line in fin: + if 'Icon=' in line: + line = "Icon=%s\n" % (target_pkgdata + 'media/mugshot.svg') + elif 'Exec=' in line: + cmd = line.split("=")[1].split(None, 1) + line = "Exec=%s" % (target_scripts + 'mugshot') + if len(cmd) > 1: + line += " %s" % cmd[1].strip() # Add script arguments back + line += "\n" + fout.write(line) + fout.flush() + fout.close() + fin.close() + os.rename(fout.name, fin.name) + except (OSError, IOError), e: + print ("ERROR: Can't find %s" % filename) + sys.exit(1) + +def compile_schemas(root, target_data): + if target_data == '/usr/': + return # /usr paths don't need this, they will be handled by dpkg + schemadir = os.path.normpath(root + target_data + 'share/glib-2.0/schemas') + if (os.path.isdir(schemadir) and + os.path.isfile('/usr/bin/glib-compile-schemas')): + os.system('/usr/bin/glib-compile-schemas "%s"' % schemadir) + + +class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto): + def run(self): + DistUtilsExtra.auto.install_auto.run(self) + + target_data = '/' + os.path.relpath(self.install_data, self.root) + '/' + target_pkgdata = target_data + 'share/mugshot/' + target_scripts = '/' + os.path.relpath(self.install_scripts, self.root) + '/' + + values = {'__mugshot_data_directory__': "'%s'" % (target_pkgdata), + '__version__': "'%s'" % self.distribution.get_version()} + update_config(self.install_lib, values) + + desktop_file = move_desktop_file(self.root, target_data, self.prefix) + update_desktop_file(desktop_file, target_pkgdata, target_scripts) + compile_schemas(self.root, target_data) + + +################################################################################## +###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ###################### +################################################################################## + +DistUtilsExtra.auto.setup( + name='mugshot', + version='0.1', + #license='GPL-3', + #author='Your Name', + #author_email='email@ubuntu.com', + #description='UI for managing …', + #long_description='Here a longer description', + #url='https://launchpad.net/mugshot', + cmdclass={'install': InstallAndUpdateDataDirectory} + ) + diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..2560f47 --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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): + 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() diff --git a/tests/test_lint.py b/tests/test_lint.py new file mode 100644 index 0000000..5d59a3e --- /dev/null +++ b/tests/test_lint.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# This file is in the public domain +### 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()