Skip to content

Commit

Permalink
Allow songlist editing of simple text tags in-place 馃啓 (#4173)
Browse files Browse the repository at this point in the history
* Allow songlist editing of simple text tags in-place 馃啓

* Remove redundant line

* Fixes
  • Loading branch information
declension committed Oct 21, 2022
1 parent 99b956c commit e8362a0
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 29 deletions.
7 changes: 7 additions & 0 deletions docs/guide/editing_tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ path* tab to populate these tags automatically. Please see
process for several songs (the process is the same).


Editing tags in-place
---------------------
As of QL 4.6, for plain, textual tags (not internal, and not *tag expressions*)
you can edit these in-place in the songlist, by navigating to the column within
the song's row, and pressing F2. Changes are immediate.


Editing tags for several songs at once
--------------------------------------

Expand Down
24 changes: 20 additions & 4 deletions quodlibet/qltk/songlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from gi.repository import Gtk, GLib, Gdk, GObject
from senf import uri2fsn

from quodlibet import app, print_w
from quodlibet import app, print_w, print_d
from quodlibet import config
from quodlibet import const
from quodlibet import qltk
Expand Down Expand Up @@ -444,7 +444,7 @@ def __init__(self, library, player=None, update=False, model_cls=PlaylistModel):
self.connect('destroy', self.__destroy)

@property
def model(self):
def model(self) -> Gtk.TreeModel:
return self.get_model()

@property
Expand Down Expand Up @@ -675,6 +675,22 @@ def __key_press(self, songlist, event, librarian, player):
elif qltk.is_accel(event, "space", "KP_Space") and player is not None:
player.paused = not player.paused
return True
elif qltk.is_accel(event, "F2"):
songs = self.get_selected_songs()
if len(songs) > 1:
print_d("Can't edit more than one")
elif songs:
path, col = songlist.get_cursor()
song = self.get_first_selected_song()
cls = type(col).__name__
if col.can_edit:
print_d(f"Let's edit this: {song} ({cls} can be edited)")
renderers = col.get_cells()
renderers[0].props.editable = True
self.set_cursor(path, col, start_editing=True)
else:
print_d(f"Can't edit {cls}. Maybe it's synthetic / numeric?")

return False

def __enqueue(self, songs):
Expand All @@ -696,7 +712,7 @@ def __columns_changed(self, *args):
SongList.headers = headers

def __column_width_changed(self, *args):
# make sure non resizable columns stay non expanding.
# make sure non-resizable columns stay non-expanding.
# gtk likes to change them sometimes
for c in self.get_columns():
if not c.get_resizable() and c.get_expand():
Expand Down Expand Up @@ -1068,7 +1084,7 @@ def set_column_headers(self, headers):
column_expands[ce[i]] = int(ce[i + 1])

for t in headers:
column = create_songlist_column(t)
column = create_songlist_column(self.model, t)
if column.get_resizable():
if t in column_widths:
column.set_fixed_width(column_widths[t])
Expand Down
20 changes: 16 additions & 4 deletions quodlibet/qltk/songlistcolumns.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright 2005 Joe Wreschnig
# 2012 Christoph Reiter
# 2011-2020 Nick Boultbee
# 2011-2022 Nick Boultbee
# 2014 Jan Path
#
# This program is free software; you can redistribute it and/or modify
Expand All @@ -12,7 +12,7 @@
from senf import fsnative, fsn2text

from quodlibet.util.string.date import format_date
from quodlibet import _
from quodlibet import _, print_d
from quodlibet import util
from quodlibet import config
from quodlibet import app
Expand All @@ -24,7 +24,7 @@
from quodlibet.qltk.x import CellRendererPixbuf


def create_songlist_column(t):
def create_songlist_column(model: Gtk.TreeModel, t):
"""Returns a SongListColumn instance for the given tag"""

if t in ["~#added", "~#mtime", "~#lastplayed", "~#laststarted"]:
Expand All @@ -42,7 +42,7 @@ def create_songlist_column(t):
elif "<" in t:
return PatternColumn(t)
elif "~" not in t and t != "title":
return NonSynthTextColumn(t)
return NonSynthTextColumn(model, t)
else:
return WideTextColumn(t)

Expand Down Expand Up @@ -101,6 +101,8 @@ def do_apply_attributes(self, tree_model, iter_, is_expander, is_expanded):


class SongListColumn(TreeViewColumnButton):
can_edit = False
"""Whether this column can support editing"""

def __init__(self, tag):
"""tag e.g. 'artist'"""
Expand Down Expand Up @@ -281,6 +283,16 @@ class NonSynthTextColumn(WideTextColumn):
"""Optimize for non-synthesized keys by grabbing them directly.
Used for any tag without a '~' except 'title'.
"""
can_edit = True

def __row_edited(self, render, path, new: str, model: Gtk.TreeModel) -> None:
print_d(f"Trying to edit {self.header_name} to {new!r}")
model[path][0][self.header_name] = new
model.path_changed(path)

def __init__(self, model, tag):
super().__init__(tag)
self._render.connect("edited", self.__row_edited, model)

def _fetch_value(self, model, iter_):
return model.get_value(iter_).get(self.header_name, "")
Expand Down
45 changes: 24 additions & 21 deletions tests/test_qltk_songlistcolumns.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
import datetime
import time

A_DATETIME = datetime.datetime(year=1999, month=5, day=1, hour=23, minute=11,
second=59)


class TSongListColumns(TestCase):
def setUp(self):
quodlibet.config.init()
self.model = object()

def tearDown(self):
quodlibet.config.quit()

def _create_col(self, t):
return create_songlist_column(self.model, t)

def _render_column(self, column, **kwargs):
view = Gtk.TreeView()
model = PlaylistModel()
Expand All @@ -44,57 +51,57 @@ def _render_column(self, column, **kwargs):
return text

def test_date(self):
column = create_songlist_column("~#added")
column = self._create_col("~#added")
self._render_column(column)

# column reuse triggers warning somwhow
column = create_songlist_column("~#added")
column = self._create_col("~#added")
self._render_column(column, **{"~#added": 100})

def test_length(self):
column = create_songlist_column("~length")
column = self._create_col("~length")
self._render_column(column)

def test_filesize(self):
column = create_songlist_column("~#filesize")
column = self._create_col("~#filesize")
self._render_column(column)

def test_rating(self):
column = create_songlist_column("~rating")
column = self._create_col("~rating")
text = self._render_column(column)
self.assertNotEqual(text, "0.67")

column = create_songlist_column("~#rating")
column = self._create_col("~#rating")
text = self._render_column(column)
self.assertEqual(text, "0.67")

def test_bitrate(self):
column = create_songlist_column("~#bitrate")
column = self._create_col("~#bitrate")
self._render_column(column)

def test_basename(self):
column = create_songlist_column("~basename")
column = self._create_col("~basename")
self._render_column(column)

def test_pattern(self):
column = create_songlist_column("<artist>-<album>")
column = self._create_col("<artist>-<album>")
self._render_column(column)

def test_artist(self):
column = create_songlist_column("artist")
column = self._create_col("artist")
self._render_column(column)

def test_people(self):
column = create_songlist_column("~people")
column = self._create_col("~people")
self._render_column(column)

def test_bpm(self):
column = create_songlist_column("bpm")
column = self._create_col("bpm")
text = self._render_column(column, **{"bpm": "123"})
self.assertEqual(text, "123")

def test_initialkey(self):
column = create_songlist_column("initialkey")
column = self._create_col("initialkey")
text = self._render_column(column, **{"initialkey": "F"})
self.assertEqual(text, "F")

Expand All @@ -103,10 +110,8 @@ def test_custom_datecol_format(self):
quodlibet.config.settext("settings", "datecolumn_timestamp_format",
format)

d = datetime.datetime(year=1999, month=5, day=1,
hour=23, minute=11, second=59)
stamp = int(time.mktime(d.timetuple()))
column = create_songlist_column("~#added")
stamp = int(time.mktime(A_DATETIME.timetuple()))
column = self._create_col("~#added")
text = self._render_column(column, **{"~#added": stamp})
self.assertEqual(text, "19990501 23:11:59 PLAINTEXT")

Expand All @@ -118,9 +123,7 @@ def test_nonconfigured_datecol_format(self):

# make sure unset config option does not result in the
# behaviour for testcase for set option above
d = datetime.datetime(year=1999, month=5, day=1,
hour=23, minute=11, second=59)
stamp = int(time.mktime(d.timetuple()))
column = create_songlist_column("~#added")
stamp = int(time.mktime(A_DATETIME.timetuple()))
column = self._create_col("~#added")
text = self._render_column(column, **{"~#added": stamp})
self.assertNotEqual(text, "19990501 23:11:59 PLAINTEXT")

0 comments on commit e8362a0

Please sign in to comment.