Last active
September 27, 2021 05:13
-
-
Save theScottyJam/ae19299c8d71a21c75f2abd96daea9f8 to your computer and use it in GitHub Desktop.
This adds a "new file" menu item to the nautilus right-click menu for when you right click on a file. Place this file inside `~/.local/share/nautilus/scripts` and name it `new`. This script is useful for when you are using nautilus in list view, because nautilus currently doesn't show this menu item when right clicking on files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# https://superuser.com/a/1251013/57284 | |
# https://gist.github.com/theScottyJam/ae19299c8d71a21c75f2abd96daea9f8 | |
from pathlib import Path | |
import tkinter as tk | |
from tkinter import ttk | |
import os | |
import shutil | |
TEMPLATES_DIR = Path('~/Templates').expanduser() | |
target_dir = None | |
def main(): | |
global target_dir | |
# The following gets the selected item(s) from nautilus. | |
selected = os.environ.get('NAUTILUS_SCRIPT_SELECTED_FILE_PATHS', os.curdir) | |
# We're only going to use the first if multiple are selected. | |
target_dir = Path( selected.split('\n')[0] ).resolve().parent | |
templates = [f.name for f in TEMPLATES_DIR.iterdir()] | |
templates.sort() | |
if templates: | |
prompt = FilenamePrompt(templates, mk_file) | |
prompt.show() | |
else: | |
window = tk.Tk() | |
window.title('Error') | |
label = tk.Label(window, text="Error: You don't have any templates inside ~/Templates") | |
label.grid(row=0) | |
label.pack(padx=10, pady=10) | |
window.protocol("WM_DELETE_WINDOW", window.quit) | |
window.mainloop() | |
window.destroy() | |
def mk_file(template, filename): | |
""" Create a new file using by coping the chosen template to `current directory/filename` """ | |
src_file = TEMPLATES_DIR/template | |
dst_file = target_dir/filename | |
if filename == '': | |
return 'Please provide a file name' | |
if not dst_file.parent.is_dir(): # If the inputed text has a "/" with an non-existant folder before it. | |
return 'That folder does not exist' | |
if dst_file.is_file(): | |
return 'That file already exists' | |
try: | |
shutil.copy2(src_file, dst_file) | |
os.utime(dst_file) | |
except Exception as e: | |
return str(e) | |
class FilenamePrompt(): | |
def __init__(self, templates, on_create_file): | |
""" | |
templates is a list of strings that will appear in the menu. | |
on_create_file is what gets called when the user pushes ok. | |
It gets passes the selected template, and the file name the user chose. | |
If an error occures, that error should return as a string that will get shown to the user. | |
Otherwise, None should be returned. | |
""" | |
self.templates = templates | |
self.on_create_file = on_create_file | |
self.filename_has_changed = False # Sets to true as soon as the user alters the content of the textbox. | |
# This can be set to true if this class wants to alter the textbox value without triggering other consequences. | |
self.ingore_textbox_change = False | |
self.error_is_shown = False | |
self.selected_template = None | |
def show(self): | |
""" Creates and displays the file naming dialog """ | |
self.window = tk.Tk() | |
self.window.style = ttk.Style() | |
self.window.style.theme_use('default') | |
self.window.title('New Document') | |
self.window.resizable(False, False) | |
self.render(self.window) | |
self.window.mainloop() | |
self.window.destroy() | |
def render(self, window): | |
""" Renders the window and attaches event listeners """ | |
self.render_body(window) | |
self.render_buttons(window) | |
# Tab order | |
self.listbox.lower() | |
self.textbox.lower() | |
self.ok_button.lower() | |
self.cancel_button.lower() | |
# Event listeners | |
window.bind('<Return>', self.apply_values) | |
window.bind('<Escape>', self.close_window) | |
window.bind('<Down>', self.select_one_down) | |
window.bind('<Up>', self.select_one_up) | |
window.protocol("WM_DELETE_WINDOW", self.close_window) | |
def render_body(self, window): | |
""" Renders the main body of the window, which includes the template choosing box | |
and the filename textbox """ | |
body = ttk.Frame(window) | |
# listbox | |
self.listbox = tk.Listbox(window, selectmode=tk.SINGLE, width=40, selectbackground='#8af', exportselection=False) | |
self.listbox.bind('<<ListboxSelect>>', self.on_select_template) | |
self.listbox.pack() | |
for template in self.templates: | |
self.listbox.insert(tk.END, template) | |
# The following auto-resizes the listbox based on the content inside. | |
self.listbox.config(height=0) | |
# textbox | |
ttk.Label(body, text="File name: ").grid(row=0) | |
self.textbox_var = tk.StringVar() | |
self.textbox_var.trace('w', self.on_textbox_change) | |
self.textbox = ttk.Entry(body, textvariable=self.textbox_var) | |
self.textbox.grid(row=0, column=1) | |
body.pack(padx=5, pady=5) | |
# error label | |
err_label_frame = ttk.Frame(window) | |
self.err_label = tk.Label(err_label_frame, fg='#a00') | |
self.err_label.grid(row=0) | |
err_label_frame.pack() | |
# autoselects the first listbox item | |
self.listbox.select_set(0) | |
self.on_select_template() | |
def render_buttons(self, window): | |
""" Renders the buttons at the bottom of the window """ | |
button_bar = ttk.Frame(window) | |
self.cancel_button = ttk.Button(button_bar, text="Cancel", width=10, command=self.close_window) | |
self.cancel_button.pack(side=tk.LEFT, padx=5, pady=5) | |
self.ok_button = ttk.Button(button_bar, text="OK", width=10, command=self.apply_values, default=tk.ACTIVE) | |
self.ok_button.pack(side=tk.LEFT, padx=5, pady=5) | |
button_bar.pack() | |
def set_error(self, text): | |
""" Displays some text to the user """ | |
self.err_label.config(text=text) | |
self.textbox.selection_range(0, tk.END) | |
self.error_is_shown = True | |
self.textbox.focus_set() | |
# Event listeners | |
def on_textbox_change(self, *args): | |
""" Called when the user changes the file name """ | |
if not self.ingore_textbox_change == True: | |
self.filename_has_changed = True | |
self.err_label.config(text='') | |
if self.textbox.get() == '': | |
# We'll allow the textbox content to change if it's currently empty. | |
self.filename_has_changed = False | |
def on_select_template(self, event=None): | |
""" Called when the user selects a different template """ | |
self.selected_template = self.templates[ int(self.listbox.curselection()[0]) ] | |
if not self.filename_has_changed: | |
self.ingore_textbox_change = True | |
self.textbox_var.set(self.selected_template) | |
self.ingore_textbox_change = False | |
self.textbox.selection_range(0, tk.END) | |
self.window.after_idle(self.textbox.focus) | |
def select_one_down(self, event): | |
self.move_selection(1) | |
def select_one_up(self, event): | |
self.move_selection(-1) | |
def move_selection(self, amount): | |
""" Changes the template selection. """ | |
cur_selection = self.listbox.curselection()[0] | |
new_selection = int(cur_selection) + amount | |
if new_selection < 0: | |
new_selection = 0 | |
if new_selection >= len(self.templates): | |
new_selection = len(self.templates) - 1 | |
self.listbox.selection_clear(cur_selection) | |
self.listbox.select_set(new_selection) | |
self.on_select_template() | |
def close_window(self, event=None): | |
self.window.quit() | |
def apply_values(self, event=None): | |
""" Usually called with OK is pushed """ | |
message = self.on_create_file(self.selected_template, self.textbox.get()) | |
if message: | |
self.set_error(message) | |
return | |
self.window.quit() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks @gene1wood for the contribution - I've gone ahead and applied your changes.