Skip to content

Instantly share code, notes, and snippets.

@Axel-Erfurt
Last active April 5, 2022 20:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Axel-Erfurt/ecadfb0d64d68717697a0bf4b52408dc to your computer and use it in GitHub Desktop.
Save Axel-Erfurt/ecadfb0d64d68717697a0bf4b52408dc to your computer and use it in GitHub Desktop.
CSV Viewer Gtk3 Python with Filter
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk, GLib
CSS = """
window {
background: #eee;
}
#csv-view {
color: #222;
font-family: "Noto Sans";
font-size: 9pt;
}
#csv-view :selected {
background: #bdf;
color: #222;
}
#csv-view header button {
color: #222;
background: #ddd;
font-weight: bold;
border: 0.1pt solid #111;
}
#csv-view :hover {
background: #444;
color: #ace;
}
#btn_open:hover ,#btn_save:hover, #btn_addrow:hover{
background: #abc;
}
#csv-view row:nth-child(odd) {
background-color: shade(@base_normal, 0.93);
}
#sublabel {
font-size: 8pt;
color: #444;
}
"""
class TreeViewFilterWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="CSV Viewer")
self.set_border_width(4)
self.current_file = ""
self.column_count = 0
self.is_changed = False
self.connect("delete-event", self.on_close)
# box
self.vbox = Gtk.Box(orientation=1, vexpand=True)
self.add(self.vbox)
self.hbox = Gtk.Box(orientation=0)
# open button
self.btn_open = Gtk.Button.new_from_icon_name("document-open", 2)
self.btn_open.set_name("btn_open")
self.btn_open.set_tooltip_text("Open File")
self.btn_open.set_hexpand(False)
self.btn_open.set_relief(2)
self.btn_open.connect("clicked", self.on_open_file)
self.hbox.pack_start(self.btn_open, False, False, 1)
# save button
self.btn_save = Gtk.Button.new_from_icon_name("document-save", 2)
self.btn_save.set_name("btn_save")
self.btn_save.set_tooltip_text("Save current File")
self.btn_save.set_hexpand(False)
self.btn_save.set_relief(2)
self.btn_save.connect("clicked", self.on_save_file)
self.hbox.pack_start(self.btn_save, False, False, 1)
# save as button
self.btn_save_as = Gtk.Button.new_from_icon_name("document-save-as", 2)
self.btn_save_as.set_name("btn_save")
self.btn_save_as.set_tooltip_text("Save As ...")
self.btn_save_as.set_hexpand(False)
self.btn_save_as.set_relief(2)
self.btn_save_as.connect("clicked", self.on_save_file_as)
self.hbox.pack_start(self.btn_save_as, False, False, 1)
# add row button
self.btn_addrow = Gtk.Button.new_from_icon_name("add", 2)
self.btn_addrow.set_name("btn_addrow")
self.btn_addrow.set_tooltip_text("insert row after selelected")
self.btn_addrow.set_hexpand(False)
self.btn_addrow.set_relief(2)
self.btn_addrow.connect("clicked", self.on_add_row)
self.hbox.pack_start(self.btn_addrow, False, False, 1)
# remove row button
self.btn_remove_row = Gtk.Button.new_from_icon_name("remove", 2)
self.btn_remove_row.set_name("btn_remove_row")
self.btn_remove_row.set_tooltip_text("remove selelected row")
self.btn_remove_row.set_hexpand(False)
self.btn_remove_row.set_relief(2)
self.btn_remove_row.connect("clicked", self.on_remove_row)
self.hbox.pack_start(self.btn_remove_row, False, False, 1)
# label
self.sub_title_label = Gtk.Label(label="Info")
self.sub_title_label.set_name("sublabel")
self.hbox.pack_start(self.sub_title_label, True, False, 1)
# search field
self.search_field = Gtk.SearchEntry()
self.search_field.set_placeholder_text("filter")
self.search_field.connect("activate", self.on_selection_button_clicked)
self.search_field.connect("search-changed", self.on_search_changed)
self.search_field.set_vexpand(False)
self.hbox.pack_end(self.search_field, False, False, 1)
self.vbox.pack_start(self.hbox, False, False, 1)
# treeview
self.treeview = Gtk.TreeView()
self.treeview.set_name("csv-view")
self.treeview.set_grid_lines(3)
self.treeview.set_reorderable(True)
self.treeview.connect("drag-data-received", self.drag_data_received)
self.treeview.connect("cursor-changed", self.onSelectionChanged)
self.treeview.connect("button-press-event", self.on_pressed)
self.my_treelist = Gtk.ScrolledWindow()
self.my_treelist.add(self.treeview)
self.vbox.pack_end(self.my_treelist, True, True, 5)
# style
provider = Gtk.CssProvider()
provider.load_from_data(bytes(CSS.encode()))
style = self.treeview.get_style_context()
screen = Gdk.Screen.get_default()
priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
style.add_provider_for_screen(screen, provider, priority)
def maybe_saved(self, *args):
print("is modified", self.is_changed)
md = Gtk.MessageDialog(title="PyEdit4", message_type=Gtk.MessageType.QUESTION,
text="The document was changed.\n\nSave changes?",
parent=None)
md.add_buttons("Cancel", Gtk.ResponseType.CANCEL,
"Yes", Gtk.ResponseType.YES, "No", Gtk.ResponseType.NO)
response = md.run()
if response == Gtk.ResponseType.YES:
### save
self.on_save_file()
md.destroy()
return False
elif response == Gtk.ResponseType.NO:
md.destroy()
return False
elif response == Gtk.ResponseType.CANCEL:
md.destroy()
return True
md.destroy()
def on_close(self, *args):
print("goodbye ...")
print(f"{self.current_file} changed: {self.is_changed}")
if self.is_changed:
b = self.maybe_saved()
print (f"close: {b}")
if b:
return True
else:
Gtk.main_quit()
else:
Gtk.main_quit()
def on_add_row(self, *args):
index = self.treeview.get_selection().get_selected_rows()[1][0][0]
self.my_liststore.insert(index + 1)
self.is_changed = True
def on_remove_row(self, *args):
model, paths = self.treeview.get_selection().get_selected_rows()
if paths:
for path in paths:
iter = self.my_liststore.get_iter(path)
self.my_liststore.remove(iter)
self.is_changed = True
def on_pressed(self, trview, event):
path, col, x, y = trview.get_path_at_pos(event.x, event.y)
self.column_index = col.colnr
self.path = path
def onSelectionChanged(self, *args):
model, pathlist = self.treeview.get_selection().get_selected()
if pathlist:
self.value_list = []
for x in range(self.column_count):
self.value_list.append(model[pathlist][x])
def on_open_file(self, *args):
dlg = Gtk.FileChooserDialog(title="Please choose a file", parent=None, action = 0)
dlg.add_buttons("Cancel", Gtk.ResponseType.CANCEL,
"Open", Gtk.ResponseType.OK)
docs = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS)
dlg.set_current_folder(docs)
filter = Gtk.FileFilter()
filter.set_name("CSV Files")
filter.add_pattern("*.csv")
dlg.add_filter(filter)
response = dlg.run()
if response == Gtk.ResponseType.OK:
self.current_file = (dlg.get_filename())
self.load_into_table(self.current_file)
dlg.destroy()
def load_into_table(self, csv_file, *args):
self.search_field.set_text("")
self.current_filter_text = ""
for column in self.treeview.get_columns():
self.treeview.remove_column(column)
my_list = []
my_csv = open(csv_file, 'r').read().splitlines()
self.column_count = len(my_csv[0].split('\t'))
self.my_liststore = Gtk.ListStore(*[str]* self.column_count)
for i, column_title in enumerate(my_csv[0].split('\t')):
renderer = Gtk.CellRendererText()
renderer.set_property('editable', True)
renderer.connect("edited", self.text_edited)
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
column.colnr = i
self.treeview.append_column(column)
for line in my_csv[1:]:
row = line.split('\t')
my_list.append(tuple(row))
for line_value in my_list:
self.my_liststore.append(list(line_value))
self.my_filter = self.my_liststore.filter_new()
self.my_filter.set_visible_func(self.visible_cb)
self.treeview.set_model(self.my_filter)
# self.treeview.set_enable_search(True)
self.sub_title_label.set_text(csv_file)
self.is_changed = False
def on_save_file_as(self, *args):
dlg = Gtk.FileChooserDialog(title="Please choose a file", parent=None, action = 1)
dlg.add_buttons("Cancel", Gtk.ResponseType.CANCEL,
"Save", Gtk.ResponseType.OK)
filter = Gtk.FileFilter()
filter.set_name("CSV Files")
filter.add_pattern("*.csv")
dlg.add_filter(filter)
dlg.set_current_name("*.csv")
response = dlg.run()
if response == Gtk.ResponseType.OK:
infile = (dlg.get_filename())
print(infile)
# model to csv_file
list_string = []
for node in self.my_liststore:
d = []
for column in range(self.column_count):
d.append(node[column])
list_string.append(d)
with open(infile, 'w') as f:
for line in list_string:
value = "\t".join(line)
f.write(f'{value}\n')
self.is_changed = False
else:
print("None")
dlg.destroy()
def on_save_file(self, *args):
if self.current_file == "":
self.on_save_file_as()
else:
# model to csv_file
list_string = []
for node in self.my_liststore:
d = []
for column in range(self.column_count):
d.append(node[column])
list_string.append(d)
with open(self.current_file, 'w') as f:
for line in list_string:
value = "\t".join(line)
f.write(f'{value}\n')
self.is_changed = False
def text_edited(self, cellrenderertext, treepath, text):
column = self.column_index
self.my_liststore[treepath][column] = text
self.is_changed = True
def drag_data_received(self, dest, selection_data, *args):
print(dest.get_drag_dest_row())
def my_filter_func(self, model, iter, data):
if (
self.current_filter_text is None
or self.current_filter_text == "None"
):
return True
else:
return model[iter][0] == self.current_filter_text
def on_selection_button_clicked(self, widget):
self.current_filter_text = widget.get_text()
self.my_filter.refilter()
def visible_cb(self, model, iter, data=None):
search_query = self.search_field.get_text().lower()
active_category = 0
search_in_all_columns = True
if search_query == "":
return True
if search_in_all_columns:
for col in range(0,self.treeview.get_n_columns()):
value = model.get_value(iter, col)
if (search_query.lower() in value
or search_query.upper() in value
or search_query.title() in value):
return True
return False
value = model.get_value(iter, active_category).lower()
return True if value.startswith(search_query) else False
def on_search_changed(self, *args):
self.on_selection_button_clicked(self.search_field)
win = TreeViewFilterWindow()
win.connect("destroy", Gtk.main_quit)
win.set_size_request(600, 300)
win.move(0, 0)
win.show_all()
win.resize(800, 500)
Gtk.main()
@Axel-Erfurt
Copy link
Author

Axel-Erfurt commented Feb 11, 2021

added editing, saving, add / remove row

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment