171 lines
6.6 KiB
Python
171 lines
6.6 KiB
Python
# window.py
|
|
#
|
|
# Copyright 2024 Benjamin Burkhardt
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from gi.repository import Adw, Gtk, Gio, GLib # Adw is Adwaita and GTK GTK :)
|
|
# Gio is needed for actions etc.
|
|
# and GLib for saving the file!
|
|
|
|
@Gtk.Template(resource_path='/com/example/textviewer/window.ui')
|
|
class TextViewerWindow(Adw.ApplicationWindow):
|
|
__gtype_name__ = 'TextViewerWindow'
|
|
|
|
main_text_view = Gtk.Template.Child()
|
|
open_button = Gtk.Template.Child()
|
|
cursor_pos_text = Gtk.Template.Child()
|
|
toast_overlay = Gtk.Template.Child()
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
open_action = Gio.SimpleAction(name="open")
|
|
open_action.connect("activate", self.open_file_dialog_action)
|
|
self.add_action(open_action)
|
|
|
|
save_action = Gio.SimpleAction(name="save-as")
|
|
save_action.connect("activate", self.save_file_dialog_action)
|
|
self.add_action(save_action)
|
|
|
|
|
|
buffer = self.main_text_view.get_buffer()
|
|
buffer.connect("notify::cursor-position", self.update_cursor_position)
|
|
|
|
self.settings = Gio.Settings(schema_id="com.example.textviewer")
|
|
self.settings.bind("window-width", self, "default-width",
|
|
Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind("window-height", self, "default-height",
|
|
Gio.SettingsBindFlags.DEFAULT)
|
|
self.settings.bind("window-maximized", self, "maximized",
|
|
Gio.SettingsBindFlags.DEFAULT)
|
|
# Gio.Settings.bind method takes
|
|
# 1. the key defined in the settings scheme to bind to
|
|
# 2. the object which owns the property to bind the setting key to
|
|
# 3. property: the property, e.g. default-width as defined in the XML!
|
|
# 4. flags: BindFlags.DEFAULT means it's a kind of bidirectional rw bound
|
|
|
|
|
|
|
|
def open_file_dialog_action(self, action, _):
|
|
native = Gtk.FileDialog()
|
|
native.open(self, None, self.on_open_response)
|
|
|
|
def on_open_response(self, dialog, result):
|
|
try:
|
|
file = dialog.open_finish(result)
|
|
except: # if the user dismisses the dialog, a GError/sth else is raised
|
|
return
|
|
# If the user selected a file...
|
|
if file is not None:
|
|
# ... open it
|
|
self.open_file(file)
|
|
|
|
def open_file(self, file):
|
|
file.load_contents_async(None, self.open_file_complete)
|
|
|
|
def open_file_complete(self, file, result):
|
|
info = file.query_info("standard::display-name", Gio.FileQueryInfoFlags.NONE)
|
|
if info:
|
|
display_name = info.get_attribute_string("standard::display-name")
|
|
else:
|
|
display_name = file.get_basename()
|
|
|
|
contents = file.load_contents_finish(result)
|
|
if not contents[0]:
|
|
path = file.peek_path()
|
|
print(f"Unable to open {path}: {contents[1]}")
|
|
self.toast_overlay.add_toast(Adw.Toast(title=f"Unable to open “{display_name}”"))
|
|
|
|
try:
|
|
text = contents[1].decode('utf-8')
|
|
except UnicodeError as err:
|
|
path = file.peek_path()
|
|
print(f"Unable to load the contents of {path}: the file is not encoded with UTF-8")
|
|
self.toast_overlay.add_toast(Adw.Toast(title=f"Invalid encoding for {display_name}"))
|
|
return
|
|
|
|
|
|
self.set_title(display_name)
|
|
|
|
buffer = self.main_text_view.get_buffer()
|
|
buffer.set_text(text)
|
|
start = buffer.get_start_iter()
|
|
buffer.place_cursor(start)
|
|
|
|
def update_cursor_position(self, buffer, _):
|
|
# Retrieve the value of the "cursor-position" property
|
|
cursor_pos = buffer.props.cursor_position
|
|
# Construct the text iterator for the position of the cursor
|
|
iter = buffer.get_iter_at_offset(cursor_pos)
|
|
line = iter.get_line() + 1
|
|
column = iter.get_line_offset() + 1
|
|
# Set the new contents of the label
|
|
self.cursor_pos_text.set_text(f"Ln {line}, Col {column}")
|
|
|
|
def save_file_dialog_action(self, action, _):
|
|
native = Gtk.FileDialog()
|
|
native.save(self, None, self.on_save_response)
|
|
|
|
def on_save_response(self, dialog, result):
|
|
try:
|
|
file = dialog.save_finish(result)
|
|
except: # if the user dismisses the dialog, a GError/sth else is raised
|
|
return
|
|
if file is not None:
|
|
self.save_file(file)
|
|
|
|
def save_file(self, file):
|
|
buffer = self.main_text_view.get_buffer()
|
|
|
|
# Retrieve the iterator at the start of the buffer
|
|
start = buffer.get_start_iter()
|
|
# Retrieve the iterator at the end of the buffer
|
|
end = buffer.get_end_iter()
|
|
# Retrieve all the visible text between the two bounds
|
|
text = buffer.get_text(start, end, False)
|
|
|
|
# If there is nothing to save, return early
|
|
if not text:
|
|
return
|
|
|
|
bytes = GLib.Bytes.new(text.encode('utf-8'))
|
|
|
|
# Start the asynchronous operation to save the data into the file
|
|
file.replace_contents_bytes_async(bytes,
|
|
None,
|
|
False,
|
|
Gio.FileCreateFlags.NONE,
|
|
None,
|
|
self.save_file_complete)
|
|
|
|
def save_file_complete(self, file, result):
|
|
res = file.replace_contents_finish(result)
|
|
info = file.query_info("standard::display-name",
|
|
Gio.FileQueryInfoFlags.NONE)
|
|
if info:
|
|
display_name = info.get_attribute_string("standard::display-name")
|
|
else:
|
|
display_name = file.get_basename()
|
|
if not res:
|
|
print(f"Unable to save {display_name}")
|
|
self.toast_overlay.add_toast(Adw.Toast(title=f"Unable to save {display_name}"))
|
|
else:
|
|
self.toast_overlay.add_toast(Adw.Toast(title=f"Saved to {display_name}"))
|
|
|
|
self.set_title(display_name)
|
|
|