Commit c9d25d2f authored by Avi Wadhwa's avatar Avi Wadhwa

shift from GtkTemplate to Gtk.Template, use in app notifications

parent 3a181cc0
......@@ -21,6 +21,11 @@
<release version="0.2" date="2019-03-31">
Get more animations throughout, shift from notifications to in-app notifications, upgrade code infrastructure (Gtk Templates)
<release version="0.103" date="2019-02-19">
Upgrade the logo
# Copyright 2015 Dustin Spicuzza <>
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
from os.path import abspath, join
import inspect
import warnings
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk
__all__ = ['GtkTemplate']
class GtkTemplateWarning(UserWarning):
def _connect_func(builder, obj, signal_name, handler_name,
connect_object, flags, cls):
'''Handles GtkBuilder signal connect events'''
if connect_object is None:
extra = ()
extra = (connect_object,)
# The handler name refers to an attribute on the template instance,
# so ask GtkBuilder for the template instance
template_inst = builder.get_object(cls.__gtype_name__)
if template_inst is None: # This should never happen
errmsg = "Internal error: cannot find template instance! obj: %s; " \
"signal: %s; handler: %s; connect_obj: %s; class: %s" % \
(obj, signal_name, handler_name, connect_object, cls)
warnings.warn(errmsg, GtkTemplateWarning)
handler = getattr(template_inst, handler_name)
if flags == GObject.ConnectFlags.AFTER:
obj.connect_after(signal_name, handler, *extra)
obj.connect(signal_name, handler, *extra)
def _register_template(cls, template_bytes):
'''Registers the template for the widget and hooks init_template'''
# This implementation won't work if there are nested templates, but
# we can't do that anyways due to PyGObject limitations so it's ok
if not hasattr(cls, 'set_template'):
raise TypeError("Requires PyGObject 3.13.2 or greater")
bound_methods = set()
bound_widgets = set()
# Walk the class, find marked callbacks and child attributes
for name in dir(cls):
o = getattr(cls, name, None)
if inspect.ismethod(o):
if hasattr(o, '_gtk_callback'):
# Don't need to call this, as connect_func always gets called
#cls.bind_template_callback_full(name, o)
elif isinstance(o, _Child):
cls.bind_template_child_full(name, True, 0)
# Have to setup a special connect function to connect at template init
# because the methods are not bound yet
cls.set_connect_func(_connect_func, cls)
cls.__gtemplate_methods__ = bound_methods
cls.__gtemplate_widgets__ = bound_widgets
base_init_template = cls.init_template
cls.init_template = lambda s: _init_template(s, cls, base_init_template)
def _init_template(self, cls, base_init_template):
'''This would be better as an override for Gtk.Widget'''
# TODO: could disallow using a metaclass.. but this is good enough
# .. if you disagree, feel free to fix it and issue a PR :)
if self.__class__ is not cls:
raise TypeError("Inheritance from classes with @GtkTemplate decorators "
"is not allowed at this time")
connected_signals = set()
self.__connected_template_signals__ = connected_signals
for name in self.__gtemplate_widgets__:
widget = self.get_template_child(cls, name)
self.__dict__[name] = widget
if widget is None:
# Bug: if you bind a template child, and one of them was
# not present, then the whole template is broken (and
# it's not currently possible for us to know which
# one is broken either -- but the stderr should show
# something useful with a Gtk-CRITICAL message)
raise AttributeError("A missing child widget was set using "
"GtkTemplate.Child and the entire "
"template is now broken (widgets: %s)" %
', '.join(self.__gtemplate_widgets__))
for name in self.__gtemplate_methods__.difference(connected_signals):
errmsg = ("Signal '%s' was declared with @GtkTemplate.Callback " +
"but was not present in template") % name
warnings.warn(errmsg, GtkTemplateWarning)
# TODO: Make it easier for IDE to introspect this
class _Child(object):
Assign this to an attribute in your class definition and it will
be replaced with a widget defined in the UI file when init_template
is called
__slots__ = []
def widgets(count):
Allows declaring multiple widgets with less typing::
button \
label1 \
label2 = GtkTemplate.Child.widgets(3)
return [_Child() for _ in range(count)]
class _GtkTemplate(object):
Use this class decorator to signify that a class is a composite
widget which will receive widgets and connect to signals as
defined in a UI template. You must call init_template to
cause the widgets/signals to be initialized from the template::
class Foo(Gtk.Box):
def __init__(self):
super(Foo, self).__init__()
The 'ui' parameter can either be a file path or a GResource resource
class Foo(Gtk.Box):
To connect a signal to a method on your instance, do::
def on_thing_happened(self, widget):
To create a child attribute that is retrieved from your template,
add this to your class definition::
class Foo(Gtk.Box):
widget = GtkTemplate.Child()
Note: This is implemented as a class decorator, but if it were
included with PyGI I suspect it might be better to do this
in the GObject metaclass (or similar) so that init_template
can be called automatically instead of forcing the user to do it.
.. note:: Due to limitations in PyGObject, you may not inherit from
python objects that use the GtkTemplate decorator.
__ui_path__ = None
def Callback(f):
Decorator that designates a method to be attached to a signal from
the template
f._gtk_callback = True
return f
Child = _Child
def set_ui_path(*path):
If using file paths instead of resources, call this *before*
loading anything that uses GtkTemplate, or it will fail to load
your template file
:param path: one or more path elements, will be joined together
to create the final path
TODO: Alternatively, could wait until first class instantiation
before registering templates? Would need a metaclass...
_GtkTemplate.__ui_path__ = abspath(join(*path))
def __init__(self, ui):
self.ui = ui
def __call__(self, cls):
if not issubclass(cls, Gtk.Widget):
raise TypeError("Can only use @GtkTemplate on Widgets")
# Nested templates don't work
if hasattr(cls, '__gtemplate_methods__'):
raise TypeError("Cannot nest template classes")
# Load the template either from a resource path or a file
# - Prefer the resource path first
template_bytes = Gio.resources_lookup_data(self.ui, Gio.ResourceLookupFlags.NONE)
except GLib.GError:
ui = self.ui
if isinstance(ui, (list, tuple)):
ui = join(ui)
if _GtkTemplate.__ui_path__ is not None:
ui = join(_GtkTemplate.__ui_path__, ui)
with open(ui, 'rb') as fp:
template_bytes =
_register_template(cls, template_bytes)
return cls
# Future shim support if this makes it into PyGI?
#if hasattr(Gtk, 'GtkTemplate'):
# GtkTemplate = lambda c: c
GtkTemplate = _GtkTemplate
......@@ -27,7 +27,6 @@ configure_file(
organizer_sources = [
......@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <>.
from gi.repository import Gtk, GLib, Handy, Gio
from .gi_composites import GtkTemplate
#from .gi_composites import GtkTemplate
import threading
import shutil
# until I realize why GLib permissions are messed up
......@@ -141,70 +141,71 @@ folders = [
# to initiate the custom libhandy widgets
class OrganizerWindow(Gtk.ApplicationWindow):
__gtype_name__ = 'OrganizerWindow'
# initializing widgets to be used later
subtitle = GtkTemplate.Child()
gtk_stack = GtkTemplate.Child()
stack_2 = GtkTemplate.Child()
file_sorting = GtkTemplate.Child()
go_back = GtkTemplate.Child()
go_back_revealer = GtkTemplate.Child()
subtitle_revealer = GtkTemplate.Child()
start_screen = GtkTemplate.Child()
header_bar = GtkTemplate.Child()
sidebar = GtkTemplate.Child()
sidebar_scrolled_window = GtkTemplate.Child()
scrolled_start_screen = GtkTemplate.Child()
subtitle = Gtk.Template.Child()
gtk_stack = Gtk.Template.Child()
stack_2 = Gtk.Template.Child()
file_sorting = Gtk.Template.Child()
go_back = Gtk.Template.Child()
go_back_revealer = Gtk.Template.Child()
subtitle_revealer = Gtk.Template.Child()
start_screen = Gtk.Template.Child()
header_bar = Gtk.Template.Child()
sidebar = Gtk.Template.Child()
sidebar_scrolled_window = Gtk.Template.Child()
scrolled_start_screen = Gtk.Template.Child()
spinner = Gtk.Spinner()
busy_title = GtkTemplate.Child()
gio_application = Gio.Application.get_default
busy_title = Gtk.Template.Child()
inappnotification_revealer = Gtk.Template.Child()
inappnotification_button = Gtk.Template.Child()
inappnotification_label = Gtk.Template.Child()
# all lists
application_list = GtkTemplate.Child()
archives_list = GtkTemplate.Child()
audio_list = GtkTemplate.Child()
ebooks_list = GtkTemplate.Child()
font_list = GtkTemplate.Child()
illustrations_list = GtkTemplate.Child()
image_list = GtkTemplate.Child()
presentations_list = GtkTemplate.Child()
spreadsheets_list = GtkTemplate.Child()
text_list = GtkTemplate.Child()
video_list = GtkTemplate.Child()
application_list = Gtk.Template.Child()
archives_list = Gtk.Template.Child()
audio_list = Gtk.Template.Child()
ebooks_list = Gtk.Template.Child()
font_list = Gtk.Template.Child()
illustrations_list = Gtk.Template.Child()
image_list = Gtk.Template.Child()
presentations_list = Gtk.Template.Child()
spreadsheets_list = Gtk.Template.Child()
text_list = Gtk.Template.Child()
video_list = Gtk.Template.Child()
# all columns
application_column = GtkTemplate.Child()
archives_column = GtkTemplate.Child()
audio_column = GtkTemplate.Child()
ebooks_column = GtkTemplate.Child()
font_column = GtkTemplate.Child()
illustrations_column = GtkTemplate.Child()
image_column = GtkTemplate.Child()
presentations_column = GtkTemplate.Child()
spreadsheets_column = GtkTemplate.Child()
text_column = GtkTemplate.Child()
video_column = GtkTemplate.Child()
application_column = Gtk.Template.Child()
archives_column = Gtk.Template.Child()
audio_column = Gtk.Template.Child()
ebooks_column = Gtk.Template.Child()
font_column = Gtk.Template.Child()
illustrations_column = Gtk.Template.Child()
image_column = Gtk.Template.Child()
presentations_column = Gtk.Template.Child()
spreadsheets_column = Gtk.Template.Child()
text_column = Gtk.Template.Child()
video_column = Gtk.Template.Child()
# all category location options
archive_location_option = GtkTemplate.Child()
ebooks_location_option = GtkTemplate.Child()
font_location_option = GtkTemplate.Child()
illustrations_location_option = GtkTemplate.Child()
application_location_option = GtkTemplate.Child()
presentations_location_option = GtkTemplate.Child()
spreadsheets_location_option = GtkTemplate.Child()
audio_location_option = GtkTemplate.Child()
image_location_option = GtkTemplate.Child()
text_location_option = GtkTemplate.Child()
video_location_option = GtkTemplate.Child()
__gtype_name__ = 'OrganizerWindow'
archive_location_option = Gtk.Template.Child()
ebooks_location_option = Gtk.Template.Child()
font_location_option = Gtk.Template.Child()
illustrations_location_option = Gtk.Template.Child()
application_location_option = Gtk.Template.Child()
presentations_location_option = Gtk.Template.Child()
spreadsheets_location_option = Gtk.Template.Child()
audio_location_option = Gtk.Template.Child()
image_location_option = Gtk.Template.Child()
text_location_option = Gtk.Template.Child()
video_location_option = Gtk.Template.Child()
def __init__(self, **kwargs):
def does_exist(self, file, directory, index):
filename, file_extension = os.path.splitext(file)
......@@ -221,9 +222,9 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.busy_title.props_active = False
newdirectory_last_name = newdirectory.split('/').pop()
move_file_successful =" files moved successfully!")
self.gio_application().send_notification(None, move_file_successful)
self.inappnotification_label.set_text("Files moved successfully")
# so the popover can popdown before anything else happens
if not len(visible_index_list)-1:
......@@ -278,10 +279,9 @@ class OrganizerWindow(Gtk.ApplicationWindow):
# Unhide the back button
already_empty_error ="Folder was already empty!")
already_empty_error.set_body("Organize another folder")
self.gio_application().send_notification(None, already_empty_error)
self.inappnotification_label.set_text("Folder was already empty")
# in app notification that app already sorted
def move_files_threading(self, directory, newdirectory, files, popover):
......@@ -291,10 +291,9 @@ class OrganizerWindow(Gtk.ApplicationWindow):
shutil.move(directory+"/"+file, newdirectory+"/"+new_file)
move_file_error ="Error occurred!")
move_file_error.set_body("Trying to move files in Organizer failed")
self.gio_application().send_notification(None, move_file_error)
self.inappnotification_label.set_text("Error occurred")
GLib.idle_add(self.mainloop_after_move, newdirectory)
# select row in front
# move to previous listboxrow
......@@ -358,6 +357,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
# Back Button
def go_back_clicked_cb(self, button):
# if is folded on content then go to sidebar, otherwise actual back to startscreen
......@@ -369,7 +369,13 @@ class OrganizerWindow(Gtk.ApplicationWindow):
# In app notification close button
def inappnotification_button_clicked_cb(self, button):
# About Menu
def on_about_button_clicked(self, button):
dialog = Gtk.AboutDialog()
......@@ -379,18 +385,19 @@ class OrganizerWindow(Gtk.ApplicationWindow):
dialog.set_comments(_('Organizes your files'))
def category_row_clicked(self, widget, row):
# When any location is clicked on homescreen
def row_activated(self, widget, row):
# loop and delete all previous all ListBoxRows
list_of_listboxes = [self.application_list,self.archives_list,
......@@ -438,6 +445,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
# separate thread to not hang up the entire GUI, and to render the spinner at the same time
thread_testing = threading.Thread(target=self.print_mimes, args=(directory,))
def archives_move_clicked(self, button):
if self.archive_location_option.get_active():
archive_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Archive files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -460,6 +468,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
if response_type:
self.move_files(directory, newdirectory, archives, button.get_parent().get_parent().get_parent())
def ebooks_move_clicked(self, button):
if self.ebooks_location_option.get_active():
ebooks_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Ebook files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -482,6 +491,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
if response_type:
self.move_files(directory, newdirectory, ebooks, button.get_parent().get_parent().get_parent())
def font_move_clicked(self, button):
if self.font_location_option.get_active():
font_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Font files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -504,6 +514,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
if response_type:
self.move_files(directory, newdirectory, font, button.get_parent().get_parent().get_parent())
def illustrations_move_clicked(self, button):
if self.illustrations_location_option.get_active():
illustrations_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Illustration files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -526,6 +537,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
if response_type:
self.move_files(directory, newdirectory, illustrations, button.get_parent().get_parent().get_parent())
def application_move_clicked(self, button):
if self.application_location_option.get_active():
application_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Other files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -549,6 +561,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, application, button.get_parent().get_parent().get_parent())
def presentations_move_clicked(self, button):
if self.presentations_location_option.get_active():
presentations_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Presentation files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -572,6 +585,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, presentations, button.get_parent().get_parent().get_parent())
def spreadsheets_move_clicked(self, button):
if self.spreadsheets_location_option.get_active():
spreadsheets_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Spreadsheet files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -595,6 +609,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, spreadsheets, button.get_parent().get_parent().get_parent())
def audio_move_clicked(self, button):
if self.audio_location_option.get_active():
audio_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Music files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -618,6 +633,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, audio, button.get_parent().get_parent().get_parent())
def image_move_clicked(self, button):
if self.image_location_option.get_active():
image_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Image files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -641,6 +657,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, image, button.get_parent().get_parent().get_parent())
def text_move_clicked(self, button):
if self.text_location_option.get_active():
text_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Document files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
......@@ -664,6 +681,7 @@ class OrganizerWindow(Gtk.ApplicationWindow):
self.move_files(directory, newdirectory, text, button.get_parent().get_parent().get_parent())
def video_move_clicked(self, button):
if self.video_location_option.get_active():
video_directory_chooser = Gtk.FileChooserDialog('Choose where to move the Video files', None, Gtk.FileChooserAction.SELECT_FOLDER, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 'Select', Gtk.ResponseType.OK))
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment