|
Packit |
1470ea |
|
|
Packit |
1470ea |
<page xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its" xmlns:xi="http://www.w3.org/2001/XInclude" type="guide" style="task" id="filechooserdialog.py" xml:lang="de">
|
|
Packit |
1470ea |
<info>
|
|
Packit |
1470ea |
<title type="text">FileChooserDialog (Python)</title>
|
|
Packit |
1470ea |
<link type="guide" xref="beginner.py#file-selectors"/>
|
|
Packit |
1470ea |
<link type="seealso" xref="gmenu.py"/>
|
|
Packit |
1470ea |
<link type="seealso" xref="toolbar_builder.py"/>
|
|
Packit |
1470ea |
<link type="seealso" xref="textview.py"/>
|
|
Packit |
1470ea |
<link type="next" xref="combobox.py"/>
|
|
Packit |
1470ea |
<revision version="0.1" date="2012-08-14" status="draft"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<credit type="author copyright">
|
|
Packit |
1470ea |
<name>Marta Maria Casetti</name>
|
|
Packit |
1470ea |
<email its:translate="no">mmcasetti@gmail.com</email>
|
|
Packit |
1470ea |
<years>2012</years>
|
|
Packit |
1470ea |
</credit>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<desc>A dialog suitable for "Open" and "Save" commands</desc>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
|
|
Packit |
1470ea |
<mal:name>Mario Blättermann</mal:name>
|
|
Packit |
1470ea |
<mal:email>mario.blaettermann@gmail.com</mal:email>
|
|
Packit |
1470ea |
<mal:years>2011, 2013</mal:years>
|
|
Packit |
1470ea |
</mal:credit>
|
|
Packit |
1470ea |
</info>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<title>FileChooserDialog</title>
|
|
Packit |
1470ea |
<media type="image" mime="image/png" src="media/filechooserdialog_save.png"/>
|
|
Packit |
1470ea |
This FileChooserDialog saves a text document, which can be opened or written from scratch in a TextView (see below).
|
|
Packit |
1470ea |
<media type="image" mime="image/png" src="media/filechooserdialog_menu.png"/>
|
|
Packit |
1470ea |
It is also possible to call a FileChooserDialog to open a new document.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<links type="sections"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="overview">
|
|
Packit |
1470ea |
<title>Steps to recreate the example</title>
|
|
Packit |
1470ea |
<steps>
|
|
Packit |
1470ea |
<item>Create a file .ui to describe an app-menu with items "New", "Open", "Save", "Save as", and "Quit". This can be done with Glade or in a text editor. See <link xref="#xml"/> </item>
|
|
Packit |
1470ea |
<item>Create a Python program for a Gtk.TextView with a Gtk.Buffer self.buffer , and a self.file which will be a Gio.File and we set initially as None . </item>
|
|
Packit |
1470ea |
<item>In this program, create also the actions corresponding to the items in the app-menu, connect them to callback functions, and import the menu in the do_startup() method with a Gtk.Builder. </item>
|
|
Packit |
1470ea |
<item>"New" and "Quit" actions and callback functions are quite straightforward, see <link xref="#code"/>. See <link xref="signals-callbacks.py"/> for a more detailed explanation of signals and callback functions. </item>
|
|
Packit |
1470ea |
<item>"Open" callback should create and open a Gtk.FileChooserDialog for "Open", connected with another callback function for each of the two "Open" and "Cancel" buttons of the FileChooserDialog. </item>
|
|
Packit |
1470ea |
<item>"Save as" works basically as "Open", but the callback function of the "Save" button depends on a more complex method save_to_file() . </item>
|
|
Packit |
1470ea |
<item>"Save" can be reduced to the case where the file is None , that is the case where self.file is a new file, which in turn is the case "Save as"; and to the case where the file is not None , which in turn is reduced to save_to_file() . </item>
|
|
Packit |
1470ea |
<item>Finally, the method save_to_file() : see <link xref="#code"/>, lines 146 - 175. </item>
|
|
Packit |
1470ea |
</steps>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="xml">
|
|
Packit |
1470ea |
<title>XML file which creates the app-menu</title>
|
|
Packit |
1470ea |
<?xml version="1.0"?>
|
|
Packit |
1470ea |
<interface>
|
|
Packit |
1470ea |
<menu id="appmenu">
|
|
Packit |
1470ea |
<section>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
<attribute name="label">New</attribute>
|
|
Packit |
1470ea |
<attribute name="action">win.new</attribute>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
<attribute name="label">Open</attribute>
|
|
Packit |
1470ea |
<attribute name="action">win.open</attribute>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
<section>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
<attribute name="label">Save</attribute>
|
|
Packit |
1470ea |
<attribute name="action">win.save</attribute>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
<attribute name="label">Save As...</attribute>
|
|
Packit |
1470ea |
<attribute name="action">win.save-as</attribute>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
<section>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
<attribute name="label">Quit</attribute>
|
|
Packit |
1470ea |
<attribute name="action">app.quit</attribute>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
</menu>
|
|
Packit |
1470ea |
</interface>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="code">
|
|
Packit |
1470ea |
<title>Code used to generate this example</title>
|
|
Packit |
1470ea |
from gi.repository import Gtk
|
|
Packit |
1470ea |
from gi.repository import Gdk
|
|
Packit |
1470ea |
from gi.repository import Gio
|
|
Packit |
1470ea |
from gi.repository import GObject
|
|
Packit |
1470ea |
import sys
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
class MyWindow(Gtk.ApplicationWindow):
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
def __init__(self, app):
|
|
Packit |
1470ea |
Gtk.Window.__init__(
|
|
Packit |
1470ea |
self, title="FileChooserDialog Example", application=app)
|
|
Packit |
1470ea |
self.set_default_size(400, 400)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# the actions for the window menu, connected to the callback functions
|
|
Packit |
1470ea |
new_action = Gio.SimpleAction.new("new", None)
|
|
Packit |
1470ea |
new_action.connect("activate", self.new_callback)
|
|
Packit |
1470ea |
self.add_action(new_action)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
open_action = Gio.SimpleAction.new("open", None)
|
|
Packit |
1470ea |
open_action.connect("activate", self.open_callback)
|
|
Packit |
1470ea |
self.add_action(open_action)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
save_action = Gio.SimpleAction.new("save", None)
|
|
Packit |
1470ea |
save_action.connect("activate", self.save_callback)
|
|
Packit |
1470ea |
self.add_action(save_action)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
save_as_action = Gio.SimpleAction.new("save-as", None)
|
|
Packit |
1470ea |
save_as_action.connect("activate", self.save_as_callback)
|
|
Packit |
1470ea |
self.add_action(save_as_action)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# the file
|
|
Packit |
1470ea |
self.file = None
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# the textview with the buffer
|
|
Packit |
1470ea |
self.buffer = Gtk.TextBuffer()
|
|
Packit |
1470ea |
textview = Gtk.TextView(buffer=self.buffer)
|
|
Packit |
1470ea |
textview.set_wrap_mode(Gtk.WrapMode.WORD)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# a scrolled window for the textview
|
|
Packit |
1470ea |
self.scrolled_window = Gtk.ScrolledWindow()
|
|
Packit |
1470ea |
self.scrolled_window.set_policy(
|
|
Packit |
1470ea |
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
Packit |
1470ea |
self.scrolled_window.add(textview)
|
|
Packit |
1470ea |
self.scrolled_window.set_border_width(5)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# add the scrolled window to the window
|
|
Packit |
1470ea |
self.add(self.scrolled_window)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback for new
|
|
Packit |
1470ea |
def new_callback(self, action, parameter):
|
|
Packit |
1470ea |
self.buffer.set_text("")
|
|
Packit |
1470ea |
print("New file created")
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback for open
|
|
Packit |
1470ea |
def open_callback(self, action, parameter):
|
|
Packit |
1470ea |
# create a filechooserdialog to open:
|
|
Packit |
1470ea |
# the arguments are: title of the window, parent_window, action,
|
|
Packit |
1470ea |
# (buttons, response)
|
|
Packit |
1470ea |
open_dialog = Gtk.FileChooserDialog("Pick a file", self,
|
|
Packit |
1470ea |
Gtk.FileChooserAction.OPEN,
|
|
Packit |
1470ea |
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
Packit |
1470ea |
Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# not only local files can be selected in the file selector
|
|
Packit |
1470ea |
open_dialog.set_local_only(False)
|
|
Packit |
1470ea |
# dialog always on top of the textview window
|
|
Packit |
1470ea |
open_dialog.set_modal(True)
|
|
Packit |
1470ea |
# connect the dialog with the callback function open_response_cb()
|
|
Packit |
1470ea |
open_dialog.connect("response", self.open_response_cb)
|
|
Packit |
1470ea |
# show the dialog
|
|
Packit |
1470ea |
open_dialog.show()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback function for the dialog open_dialog
|
|
Packit |
1470ea |
def open_response_cb(self, dialog, response_id):
|
|
Packit |
1470ea |
open_dialog = dialog
|
|
Packit |
1470ea |
# if response is "ACCEPT" (the button "Open" has been clicked)
|
|
Packit |
1470ea |
if response_id == Gtk.ResponseType.ACCEPT:
|
|
Packit |
1470ea |
# self.file is the file that we get from the FileChooserDialog
|
|
Packit |
1470ea |
self.file = open_dialog.get_file()
|
|
Packit |
1470ea |
# an empty string (provisionally)
|
|
Packit |
1470ea |
content = ""
|
|
Packit |
1470ea |
try:
|
|
Packit |
1470ea |
# load the content of the file into memory:
|
|
Packit |
1470ea |
# success is a boolean depending on the success of the operation
|
|
Packit |
1470ea |
# content is self-explanatory
|
|
Packit |
1470ea |
# etags is an entity tag (can be used to quickly determine if the
|
|
Packit |
1470ea |
# file has been modified from the version on the file system)
|
|
Packit |
1470ea |
[success, content, etags] = self.file.load_contents(None)
|
|
Packit |
1470ea |
except GObject.GError as e:
|
|
Packit |
1470ea |
print("Error: " + e.message)
|
|
Packit |
1470ea |
# set the content as the text into the buffer
|
|
Packit |
1470ea |
self.buffer.set_text(content, len(content))
|
|
Packit |
1470ea |
print("opened: " + open_dialog.get_filename())
|
|
Packit |
1470ea |
# if response is "CANCEL" (the button "Cancel" has been clicked)
|
|
Packit |
1470ea |
elif response_id == Gtk.ResponseType.CANCEL:
|
|
Packit |
1470ea |
print("cancelled: FileChooserAction.OPEN")
|
|
Packit |
1470ea |
# destroy the FileChooserDialog
|
|
Packit |
1470ea |
dialog.destroy()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback function for save_as
|
|
Packit |
1470ea |
def save_as_callback(self, action, parameter):
|
|
Packit |
1470ea |
# create a filechooserdialog to save:
|
|
Packit |
1470ea |
# the arguments are: title of the window, parent_window, action,
|
|
Packit |
1470ea |
# (buttons, response)
|
|
Packit |
1470ea |
save_dialog = Gtk.FileChooserDialog("Pick a file", self,
|
|
Packit |
1470ea |
Gtk.FileChooserAction.SAVE,
|
|
Packit |
1470ea |
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
Packit |
1470ea |
Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT))
|
|
Packit |
1470ea |
# the dialog will present a confirmation dialog if the user types a file name that
|
|
Packit |
1470ea |
# already exists
|
|
Packit |
1470ea |
save_dialog.set_do_overwrite_confirmation(True)
|
|
Packit |
1470ea |
# dialog always on top of the textview window
|
|
Packit |
1470ea |
save_dialog.set_modal(True)
|
|
Packit |
1470ea |
# if self.file has already been saved
|
|
Packit |
1470ea |
if self.file is not None:
|
|
Packit |
1470ea |
try:
|
|
Packit |
1470ea |
# set self.file as the current filename for the file chooser
|
|
Packit |
1470ea |
save_dialog.set_file(self.file)
|
|
Packit |
1470ea |
except GObject.GError as e:
|
|
Packit |
1470ea |
print("Error: " + e.message)
|
|
Packit |
1470ea |
# connect the dialog to the callback function save_response_cb()
|
|
Packit |
1470ea |
save_dialog.connect("response", self.save_response_cb)
|
|
Packit |
1470ea |
# show the dialog
|
|
Packit |
1470ea |
save_dialog.show()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback function for the dialog save_dialog
|
|
Packit |
1470ea |
def save_response_cb(self, dialog, response_id):
|
|
Packit |
1470ea |
save_dialog = dialog
|
|
Packit |
1470ea |
# if response is "ACCEPT" (the button "Save" has been clicked)
|
|
Packit |
1470ea |
if response_id == Gtk.ResponseType.ACCEPT:
|
|
Packit |
1470ea |
# self.file is the currently selected file
|
|
Packit |
1470ea |
self.file = save_dialog.get_file()
|
|
Packit |
1470ea |
# save to file (see below)
|
|
Packit |
1470ea |
self.save_to_file()
|
|
Packit |
1470ea |
# if response is "CANCEL" (the button "Cancel" has been clicked)
|
|
Packit |
1470ea |
elif response_id == Gtk.ResponseType.CANCEL:
|
|
Packit |
1470ea |
print("cancelled: FileChooserAction.SAVE")
|
|
Packit |
1470ea |
# destroy the FileChooserDialog
|
|
Packit |
1470ea |
dialog.destroy()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback function for save
|
|
Packit |
1470ea |
def save_callback(self, action, parameter):
|
|
Packit |
1470ea |
# if self.file is not already there
|
|
Packit |
1470ea |
if self.file is not None:
|
|
Packit |
1470ea |
self.save_to_file()
|
|
Packit |
1470ea |
# self.file is a new file
|
|
Packit |
1470ea |
else:
|
|
Packit |
1470ea |
# use save_as
|
|
Packit |
1470ea |
self.save_as_callback(action, parameter)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# save_to_file
|
|
Packit |
1470ea |
def save_to_file(self):
|
|
Packit |
1470ea |
# get the content of the buffer, without hidden characters
|
|
Packit |
1470ea |
[start, end] = self.buffer.get_bounds()
|
|
Packit |
1470ea |
current_contents = self.buffer.get_text(start, end, False)
|
|
Packit |
1470ea |
# if there is some content
|
|
Packit |
1470ea |
if current_contents != "":
|
|
Packit |
1470ea |
# set the content as content of self.file.
|
|
Packit |
1470ea |
# arguments: contents, etags, make_backup, flags, GError
|
|
Packit |
1470ea |
try:
|
|
Packit |
1470ea |
self.file.replace_contents(current_contents,
|
|
Packit |
1470ea |
None,
|
|
Packit |
1470ea |
False,
|
|
Packit |
1470ea |
Gio.FileCreateFlags.NONE,
|
|
Packit |
1470ea |
None)
|
|
Packit |
1470ea |
print("saved: " + self.file.get_path())
|
|
Packit |
1470ea |
except GObject.GError as e:
|
|
Packit |
1470ea |
print("Error: " + e.message)
|
|
Packit |
1470ea |
# if the contents are empty
|
|
Packit |
1470ea |
else:
|
|
Packit |
1470ea |
# create (if the file does not exist) or overwrite the file in readwrite mode.
|
|
Packit |
1470ea |
# arguments: etags, make_backup, flags, GError
|
|
Packit |
1470ea |
try:
|
|
Packit |
1470ea |
self.file.replace_readwrite(None,
|
|
Packit |
1470ea |
False,
|
|
Packit |
1470ea |
Gio.FileCreateFlags.NONE,
|
|
Packit |
1470ea |
None)
|
|
Packit |
1470ea |
print("saved: " + self.file.get_path())
|
|
Packit |
1470ea |
except GObject.GError as e:
|
|
Packit |
1470ea |
print("Error: " + e.message)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
class MyApplication(Gtk.Application):
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
def __init__(self):
|
|
Packit |
1470ea |
Gtk.Application.__init__(self)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
def do_activate(self):
|
|
Packit |
1470ea |
win = MyWindow(self)
|
|
Packit |
1470ea |
win.show_all()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
def do_startup(self):
|
|
Packit |
1470ea |
Gtk.Application.do_startup(self)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# app action quit, connected to the callback function
|
|
Packit |
1470ea |
quit_action = Gio.SimpleAction.new("quit", None)
|
|
Packit |
1470ea |
quit_action.connect("activate", self.quit_callback)
|
|
Packit |
1470ea |
self.add_action(quit_action)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# get the menu from the ui file with a builder
|
|
Packit |
1470ea |
builder = Gtk.Builder()
|
|
Packit |
1470ea |
try:
|
|
Packit |
1470ea |
builder.add_from_file("filechooserdialog.ui")
|
|
Packit |
1470ea |
except:
|
|
Packit |
1470ea |
print("file not found")
|
|
Packit |
1470ea |
sys.exit()
|
|
Packit |
1470ea |
menu = builder.get_object("appmenu")
|
|
Packit |
1470ea |
self.set_app_menu(menu)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# callback function for quit
|
|
Packit |
1470ea |
def quit_callback(self, action, parameter):
|
|
Packit |
1470ea |
self.quit()
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
app = MyApplication()
|
|
Packit |
1470ea |
exit_status = app.run(sys.argv)
|
|
Packit |
1470ea |
sys.exit(exit_status)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="methods">
|
|
Packit |
1470ea |
<title>Useful methods for a FileChooserDialog</title>
|
|
Packit |
1470ea |
Note that the action of the FileChooserDialog can be one of the following: Gtk.FileChooserAction.OPEN (the file chooser will only let the user pick an existing file) Gtk.FileChooserAction.SAVE (the file chooser will let the user pick an existing file, or type in a new filename), Gtk.FileChooserAction.SELECT_FOLDER (the file chooser will let the user pick an existing folder), Gtk.FileChooserAction.CREATE_FOLDER (the file chooser will let the user name an existing or new folder).
|
|
Packit |
1470ea |
Besides the methods used in the <link xref="#code"/>, we have:
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>set_show_hidden(True) is used to display hidden files and folders. </item>
|
|
Packit |
1470ea |
<item>set_select_multiple(True) sets that multiple files can be selected. This is only relevant if the mode is Gtk.FileChooserAction.OPEN or Gtk.FileChooserAction.SELECT_FOLDER . </item>
|
|
Packit |
1470ea |
<item>In a "Save as" dialog, set_current_name(current_name) sets current_name in the file selector, as if entered by the user; current_name can be something like Untitled.txt. This method should not be used except in a "Save as" dialog. </item>
|
|
Packit |
1470ea |
<item>The default current folder is "recent items". To set another folder use set_current_folder_uri(uri) ; but note you should use this method and cause the file chooser to show a specific folder only when you are doing a "Save as" command and you already have a file saved somewhere. </item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="references">
|
|
Packit |
1470ea |
<title>API-Referenzen</title>
|
|
Packit |
1470ea |
In this sample we used the following:
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkFileChooserDialog.html">GtkFileChooserDialog</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkFileChooser.html">GtkFileChooser</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkWindow.html">GtkWindow</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkTextView.html">GtkTextView</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkTextBuffer.html">GtkTextBuffer</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkScrolledWindow.html">GtkScrolledWindow</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gio/stable/GFile.html">GFile</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gio/stable/GSimpleAction.html">GSimpleAction</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://developer.gnome.org/gtk3/stable/GtkBuilder.html">GtkBuilder</link> </item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
</page>
|