mirror of
https://github.com/Apress/foundations-of-pygtk-development.git
synced 2025-07-21 11:51:10 +02:00
386 lines
14 KiB
Python
386 lines
14 KiB
Python
#!/usr/bin/python3
|
|
|
|
import sys
|
|
import os, time
|
|
import gi
|
|
gi.require_version('Gtk', '3.0')
|
|
from gi.repository import Gtk, GdkPixbuf, GObject
|
|
|
|
ICON = 0
|
|
FILENAME = 1
|
|
SIZE = 2
|
|
MODIFIED = 3
|
|
|
|
size_type = ["B", "KiB", "MiB", "GiB"]
|
|
|
|
class History():
|
|
|
|
def __init__(self):
|
|
self.stack = []
|
|
self.pos = -1
|
|
|
|
class Application(Gtk.Application):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(application_id="org.example.myapp", *args,
|
|
**kwargs)
|
|
self.window = None
|
|
self.dialog = None
|
|
self.builder = None
|
|
self.treeview = None
|
|
self.history = History()
|
|
self.statusbar = None
|
|
|
|
def do_activate(self):
|
|
if not self.window:
|
|
self.builder = Gtk.Builder()
|
|
self.builder.add_from_file("./FileBrowser.glade")
|
|
self.window = self.builder.get_object("window")
|
|
self.treeview = self.builder.get_object("treeview")
|
|
|
|
# Glade does not allow us to add non-GTK items to the user data
|
|
# for connection. Also, the Gtk.Builder widget is absent from
|
|
# Glade as well. Therefore we have to make our connections to
|
|
# the signal handlers manually.
|
|
info = self.builder.get_object("info")
|
|
info.connect_after("clicked", self.on_info_clicked, self.builder,
|
|
self.history)
|
|
|
|
self.treeview.connect_after("row_activated", self.on_row_activated,
|
|
self.builder, self.history)
|
|
|
|
back = self.builder.get_object("back")
|
|
back.connect_after("clicked", self.on_back_clicked, self.builder,
|
|
self.history)
|
|
|
|
forward = self.builder.get_object("forward")
|
|
forward.connect_after("clicked", self.on_forward_clicked, self.builder,
|
|
self.history)
|
|
|
|
up = self.builder.get_object("up")
|
|
up.connect_after("clicked", self.on_up_clicked, self.builder,
|
|
self.history)
|
|
|
|
home = self.builder.get_object("home")
|
|
home.connect_after("clicked", self.on_home_clicked, self.builder,
|
|
self.history)
|
|
|
|
delete = self.builder.get_object("delete")
|
|
delete.connect_after("clicked", self.on_delete_clicked, self.builder)
|
|
|
|
go = self.builder.get_object("go")
|
|
go.connect_after("clicked", self.on_go_clicked, self.builder,
|
|
self.history)
|
|
|
|
entry = self.builder.get_object("location")
|
|
entry.connect_after("activate", self.on_location_activate, self.builder,
|
|
self.history)
|
|
|
|
refresh = self.builder.get_object("refresh")
|
|
refresh.connect_after("clicked", self.on_refresh_clicked, self.builder)
|
|
|
|
self.setup_tree_view(self.treeview)
|
|
self.setup_tree_model(self.treeview)
|
|
self.add_window(self.window)
|
|
self.window.show_all()
|
|
|
|
def setup_tree_view(self, treeview):
|
|
# Create a tree view column with an icon and file name.
|
|
column = Gtk.TreeViewColumn.new()
|
|
column.set_title("File Name")
|
|
|
|
renderer = Gtk.CellRendererPixbuf.new()
|
|
column.pack_start(renderer, False)
|
|
column.set_attributes(renderer, pixbuf=ICON)
|
|
|
|
renderer = Gtk.CellRendererText.new()
|
|
column.pack_start(renderer, True)
|
|
column.set_attributes(renderer, text=FILENAME)
|
|
|
|
treeview.append_column(column)
|
|
|
|
# Insert a second tree view column that displays the file size.
|
|
renderer = Gtk.CellRendererText.new()
|
|
column = Gtk.TreeViewColumn.new()
|
|
column.set_title("Size")
|
|
column.pack_start(renderer, True)
|
|
column.set_attributes(renderer, text=SIZE)
|
|
treeview.append_column(column)
|
|
|
|
# Insert a third tree view column that displays the last modified time/date.
|
|
renderer = Gtk.CellRendererText.new()
|
|
column = Gtk.TreeViewColumn.new()
|
|
column.set_title("Last Modified")
|
|
column.pack_start(renderer, True)
|
|
column.set_attributes(renderer, text=MODIFIED)
|
|
treeview.append_column(column)
|
|
|
|
def setup_tree_model(self, treeview):
|
|
store = Gtk.ListStore.new((GdkPixbuf.Pixbuf, GObject.TYPE_STRING,
|
|
GObject.TYPE_STRING, GObject.TYPE_STRING))
|
|
treeview.set_model(store)
|
|
self.populate_tree_model(self.builder)
|
|
|
|
def populate_tree_model(self, builder):
|
|
treeview = builder.get_object("treeview")
|
|
total_size = 0
|
|
items = 0
|
|
store = treeview.get_model()
|
|
store.clear()
|
|
location = os.getcwd()
|
|
|
|
# If the current location is not the root directory, add the '..' entry.
|
|
if len(location) > 1:
|
|
icon_theme = Gtk.IconTheme.get_default()
|
|
icon = icon_theme.load_icon("folder", -1,
|
|
Gtk.IconLookupFlags.FORCE_SIZE)
|
|
iter = store.append()
|
|
store.set(iter, (ICON, FILENAME), (icon, ".."))
|
|
|
|
# Return if the path does not exist.
|
|
if not os.path.isdir(location):
|
|
self.file_manager_error("The path %s does not exist!" % (location,), builder)
|
|
|
|
# Display the new location in the address bar.
|
|
entry = builder.get_object("location")
|
|
entry.set_text(location)
|
|
|
|
# Add each file to the list along with the file size and modified date.
|
|
pixbuf_dir = icon
|
|
pixbuf_file = icon_theme.load_icon("text-x-generic", -1,
|
|
Gtk.IconLookupFlags.FORCE_SIZE)
|
|
files = os.listdir(location)
|
|
for file in files:
|
|
fn = location + "/" + file
|
|
st = os.stat(fn)
|
|
if st:
|
|
# Calculate the file size and order of magnitude.
|
|
i = 0
|
|
size = st.st_size
|
|
total_size = size;
|
|
while size >= 1024.0:
|
|
size = size / 1024.0
|
|
i += 1
|
|
|
|
# Create strings for the file size and last modified date.
|
|
filesize = "%.1f %s" % (size, size_type[i])
|
|
modified = time.ctime(st.st_mtime)
|
|
|
|
# Add the file and its properties as a new tree view row.
|
|
iter = store.append()
|
|
|
|
if os.path.isdir(fn):
|
|
store.set(iter, (ICON, FILENAME, SIZE, MODIFIED),
|
|
(pixbuf_dir, file, filesize, modified))
|
|
else:
|
|
store.set(iter, (ICON, FILENAME, SIZE, MODIFIED),
|
|
(pixbuf_file, file, filesize, modified))
|
|
items += 1
|
|
|
|
# Calculate the total size of the directory content.
|
|
i = 0
|
|
while total_size >= 1024.0:
|
|
total_size = total_size / 1024.0
|
|
i += 1
|
|
|
|
# Add the number of items and the total size of the directory content
|
|
# to the status bar.
|
|
statusbar = builder.get_object("statusbar")
|
|
message = "%d items, Total Size: %.1f %s" % (items,
|
|
total_size, size_type[i])
|
|
context_id = statusbar.get_context_id("FileBrowser")
|
|
statusbar.pop(context_id)
|
|
statusbar.push(context_id, message)
|
|
|
|
def file_manager_error(self, message, builder):
|
|
window = builder.get_object("window")
|
|
dialog = builder.get_object("dialog")
|
|
dialog = Gtk.MessageDialog(parent=window, modal=True,
|
|
message_type=Gtk.MessageType.ERROR,
|
|
buttons=("OK", Gtk.ResponseType.OK),
|
|
title="File Manager Error")
|
|
dialog.format_secondary_text(message)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def on_info_clicked(self, button, builder, history):
|
|
treeview = builder.get_object("treeview")
|
|
dialog = builder.get_object("dialog")
|
|
selection = treeview.get_selection()
|
|
|
|
(model, iter) = selection.get_selected()
|
|
if iter:
|
|
name = builder.get_object("name")
|
|
loc = builder.get_object("location")
|
|
type = builder.get_object("type")
|
|
size = builder.get_object("size")
|
|
mod = builder.get_object("modified")
|
|
accessed = builder.get_object("accessed")
|
|
(file,) = model.get(iter, FILENAME)
|
|
|
|
# Set the file name, location and whether it is a file or directory.
|
|
location = os.getcwd()
|
|
name.set_text(file)
|
|
loc.set_text(location)
|
|
file = location + "/" + file
|
|
if os.path.isdir(file):
|
|
type.set_text("Directory")
|
|
else:
|
|
type.set_text("File")
|
|
|
|
# Set the file size, last modified date and last accessed date.
|
|
st = os.stat(file)
|
|
if st:
|
|
i = 0
|
|
file_size = st.st_size
|
|
while file_size >= 1024.0:
|
|
file_size = file_size / 1024.0
|
|
i += 1
|
|
|
|
modified = time.ctime(st.st_mtime)
|
|
access = time.ctime(st.st_atime)
|
|
mod.set_text(modified)
|
|
accessed.set_text(access)
|
|
size.set_text("%.1f %s" % (file_size, size_type[i]))
|
|
|
|
dialog.run()
|
|
dialog.hide()
|
|
|
|
def on_back_clicked(self, button, builder, history):
|
|
if len(history.stack) > 0:
|
|
if history.pos > -1:
|
|
previous_loc = history.stack[history.pos]
|
|
history.pos -= 1
|
|
os.chdir(previous_loc)
|
|
self.populate_tree_model(builder)
|
|
|
|
def on_forward_clicked(self, button, builder, history ):
|
|
if history.pos < len(history.stack) - 1:
|
|
history.pos += 1
|
|
next_loc = history.stack[history.pos]
|
|
os.chdir(next_loc)
|
|
self.populate_tree_model(builder)
|
|
|
|
def on_up_clicked(self, button, builder, history):
|
|
os.chdir("..")
|
|
location = os.getcwd()
|
|
|
|
entry = builder.get_object("location")
|
|
entry.set_text(location)
|
|
go = builder.get_object("go")
|
|
self.on_go_clicked(go, builder, history)
|
|
|
|
def on_refresh_clicked(self, button, builder):
|
|
self.populate_tree_model(builder)
|
|
|
|
def on_home_clicked(self, button, builder, history):
|
|
go = builder.get_object("go")
|
|
entry = builder.get_object("location")
|
|
entry.set_text(os.getenv("HOME"))
|
|
self.on_go_clicked(go, builder, history)
|
|
|
|
def on_delete_clicked(self, button, builder):
|
|
entry = builder.get_object("location")
|
|
treeview = builder.get_object("treeview")
|
|
window = builder.get_object("window")
|
|
location = os.getcwd()
|
|
|
|
# If there is a selected file, delete it if the user approves.
|
|
selection = treeview.get_selection()
|
|
(model, iter) = selection.get_selected()
|
|
if iter:
|
|
(file,) = model.get(iter, FILENAME)
|
|
if file == "..":
|
|
self.file_manager_error("You cannot remove the '..' directory!",
|
|
builder)
|
|
return
|
|
|
|
entry.set_text(location)
|
|
rmlocation = location + "/" + file
|
|
|
|
# Ask the user if it is okay to remove the file or folder.
|
|
msg = "Do you really want to delete %s?" % file
|
|
dialog = Gtk.MessageDialog(parent=window, modal=True,
|
|
message_type=Gtk.MessageType.QUESTION,
|
|
buttons=("Yes", Gtk.ResponseType.YES,
|
|
"No", Gtk.ResponseType.NO),
|
|
text=msg)
|
|
|
|
# If the user approves, remove the file or report ane error.
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.YES:
|
|
if os.path.isdir(rmlocation):
|
|
try:
|
|
shutil.rmtree(rmlocation)
|
|
except Exception as e:
|
|
self.file_manager_error(
|
|
"The file or folder could not be removed!", builder)
|
|
self.populate_tree_model(builder)
|
|
else:
|
|
try:
|
|
os.remove(rmlocation)
|
|
except Exception as e:
|
|
self.file_manager_error(
|
|
"The file or folder could not be removed!", builder)
|
|
self.populate_tree_model(builder)
|
|
dialog.destroy()
|
|
|
|
def on_go_clicked(self, button, builder, history):
|
|
treeview = builder.get_object("treeview")
|
|
entry = builder.get_object("location")
|
|
location = entry.get_text()
|
|
|
|
# If the directory exists, visit the entered location.
|
|
if os.path.isdir(location):
|
|
self.store_history(history)
|
|
os.chdir(location)
|
|
self.populate_tree_model(builder)
|
|
else:
|
|
self.file_manager_error("The location does not exist!", builder)
|
|
|
|
def on_location_activate(self, button, builder, history):
|
|
go = builder.get_object("go")
|
|
self.on_go_clicked(go, builder, history)
|
|
|
|
def on_row_activated(self, treeview, path, column, builder, history):
|
|
treeview = builder.get_object("treeview")
|
|
model = treeview.get_model()
|
|
iter = model.get_iter(path)
|
|
if iter:
|
|
(file,) = model.get(iter, FILENAME)
|
|
|
|
# Move to the parent directory.
|
|
if file == "..":
|
|
self.store_history(history)
|
|
os.chdir("..")
|
|
self.populate_tree_model(builder)
|
|
# Move to the chosen directory or show more information about the file.
|
|
else:
|
|
location = os.getcwd() + "/" + file
|
|
if os.path.isdir(location):
|
|
self.store_history(history)
|
|
os.chdir(location)
|
|
self.populate_tree_model(builder)
|
|
else:
|
|
info = builder.get_object("info")
|
|
self.on_info_clicked(info, builder, history)
|
|
|
|
def store_history(self, history):
|
|
location = os.getcwd()
|
|
history.stack.append(location)
|
|
history.pos = len(history.stack) - 1
|
|
|
|
def pop_history(self, history):
|
|
if len(history.stack) > 0:
|
|
history.stack.pop()
|
|
if history.stack.pos > len(history.stack) - 1:
|
|
history.pos = len(history.stack) - 1
|
|
else:
|
|
history.pos -= 1
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = Application()
|
|
app.run(sys.argv)
|