-
Notifications
You must be signed in to change notification settings - Fork 219
/
scanbox.py
166 lines (141 loc) · 6.67 KB
/
scanbox.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# Copyright 2004-2021 Joe Wreschnig, Michael Urman, Iñigo Serna,
# Steven Robertson, Nick Boultbee
#
# 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 2 of the License, or
# (at your option) any later version.
from gi.repository import Gtk
from gi.repository import Pango
from quodlibet.qltk.msg import ConfirmationPrompt
from senf import fsn2text
from quodlibet import _, print_w, print_d, app, ngettext
from quodlibet.qltk.chooser import choose_folders, _get_chooser, _run_chooser
from quodlibet.qltk.views import RCMHintedTreeView
from quodlibet.qltk.models import ObjectStore
from quodlibet.qltk.x import MenuItem, Button
from quodlibet.qltk import Icons
from quodlibet.util.path import unexpand
from quodlibet.util.library import get_scan_dirs, set_scan_dirs
from quodlibet.util import connect_obj, copool
class ScanBox(Gtk.HBox):
"""A box for editing the Library's scan directories"""
def __init__(self):
super().__init__(spacing=6)
self.model = model = ObjectStore()
self.view = view = RCMHintedTreeView(model=model)
view.set_fixed_height_mode(True)
view.set_headers_visible(False)
menu = Gtk.Menu()
remove_item = MenuItem(_("_Remove"), Icons.LIST_REMOVE)
menu.append(remove_item)
menu.show_all()
view.connect("popup-menu", self.__popup, menu)
connect_obj(remove_item, "activate", self.__remove, view)
sw = Gtk.ScrolledWindow()
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.set_shadow_type(Gtk.ShadowType.IN)
sw.add(view)
sw.set_size_request(-1, max(sw.size_request().height, 80))
sw.set_tooltip_text(_("Songs in the listed folders will be added "
"to the library during a library refresh"))
render = Gtk.CellRendererText()
render.set_property("ellipsize", Pango.EllipsizeMode.END)
def cdf(column, cell, model, iter_, data):
path = model.get_value(iter_)
cell.set_property("text", fsn2text(unexpand(path)))
column = Gtk.TreeViewColumn(None, render)
column.set_cell_data_func(render, cdf)
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
view.append_column(column)
add = Button(_("_Add"), Icons.LIST_ADD)
add.set_tooltip_text(_("The new directory will be scanned after adding"))
add.connect("clicked", self.__add)
remove = Button(_("_Remove"), Icons.LIST_REMOVE)
remove.set_tooltip_text(_("All songs in the selected directories "
"will also be removed from the library"))
move = Button(_("_Move"), Icons.EDIT_REDO)
move.connect("clicked", self.__move)
move.set_tooltip_text(_("Move a scan root (but not the files), "
"migrating metadata for all included tracks."))
selection = view.get_selection()
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
selection.connect("changed", self.__select_changed, remove, move)
selection.emit("changed")
connect_obj(remove, "clicked", self.__remove, view)
vbox = Gtk.VBox(spacing=6)
vbox.pack_start(add, False, True, 0)
vbox.pack_start(remove, False, True, 0)
vbox.pack_start(move, False, True, 0)
self.pack_start(sw, True, True, 0)
self.pack_start(vbox, False, True, 0)
for path in get_scan_dirs():
model.append(row=[path])
for child in self.get_children():
child.show_all()
def __popup(self, view, menu):
return view.popup_menu(menu, 0, Gtk.get_current_event_time())
def __select_changed(self, selection, remove_button, move_button):
remove_button.set_sensitive(selection.count_selected_rows())
move_button.set_sensitive(selection.count_selected_rows() == 1)
def __save(self):
set_scan_dirs(list(self.model.itervalues()))
def __remove(self, view) -> None:
selection = self.view.get_selection()
model, paths = selection.get_selected_rows()
gone_dirs = [model[p][0] for p in paths or []]
total = len(gone_dirs)
if not total:
return
msg = (_("Remove {dir!r} and all its tracks?").format(dir=gone_dirs[0])
if total == 1
else _("Remove {n} library paths and their tracks?").format(n=total))
title = ngettext("Remove library path?", "Remove library paths?", total)
prompt = ConfirmationPrompt(self, title, msg, _("Remove"),
ok_button_icon=Icons.LIST_REMOVE)
if prompt.run() == ConfirmationPrompt.RESPONSE_INVOKE:
view.remove_selection()
self.__save()
def __add(self, *args):
fns = choose_folders(self, _("Select Directories"), _("_Add Folders"))
for fn in fns:
self.model.append(row=[fn])
self.__save()
def __move(self, widget):
selection = self.view.get_selection()
model, paths = selection.get_selected_rows()
rows = [model[p] for p in paths or []]
if len(rows) > 1:
print_w("Can't do multiple moves at once")
return
elif not rows:
return
base_dir = rows[0][0]
chooser = _get_chooser(_("Select This Directory"), _("_Cancel"))
chooser.set_title(
_("Select Actual / New Directory for {dir!r}").format(dir=base_dir))
chooser.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
chooser.set_local_only(True)
chooser.set_select_multiple(False)
results = _run_chooser(self, chooser)
if not results:
return
new_dir = results[0]
desc = (_("This will move QL metadata:\n\n"
"{old!r} → {new!r}\n\n"
"The audio files themselves are not moved by this.\n"
"Nonetheless, a backup is recommended "
"(including the Quod Libet 'songs' file).")
.format(old=base_dir, new=new_dir))
title = _("Move scan root {dir!r}?").format(dir=base_dir)
value = ConfirmationPrompt(self, title=title, description=desc,
ok_button_text=_("OK, move it!")).run()
if value != ConfirmationPrompt.RESPONSE_INVOKE:
print_d("User aborted")
return
print_d(f"Migrate from {base_dir} -> {new_dir}")
copool.add(app.librarian.move_root, base_dir, new_dir)
path = paths[0]
self.model[path] = [new_dir]
self.model.row_changed(path, rows[0].iter)
self.__save()