prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Add option to turn columns on/off in List View
  When right-clicking to table headbar in List View, you expect to get a menu for turning columns on/off, but you just select first game in the list.
  
  </issue>
  <code>
  [start of lutris/gui/views/list.py]
  1 """TreeView based game list"""
  2 from gettext import gettext as _
  3 
  4 # Third Party Libraries
  5 # pylint: disable=no-member
  6 from gi.repository import Gtk, Pango
  7 
  8 # Lutris Modules
  9 from lutris import settings
  10 from lutris.gui.views import (
  11     COL_ICON, COL_INSTALLED_AT, COL_INSTALLED_AT_TEXT, COL_LASTPLAYED, COL_LASTPLAYED_TEXT, COL_NAME, COL_PLATFORM,
  12     COL_PLAYTIME, COL_PLAYTIME_TEXT, COL_RUNNER_HUMAN_NAME, COL_YEAR, COLUMN_NAMES
  13 )
  14 from lutris.gui.views.base import GameView
  15 from lutris.gui.views.store import sort_func
  16 
  17 
  18 class GameListView(Gtk.TreeView, GameView):
  19 
  20     """Show the main list of games."""
  21 
  22     __gsignals__ = GameView.__gsignals__
  23 
  24     def __init__(self, store):
  25         self.game_store = store
  26         self.model = self.game_store.modelsort
  27         super().__init__(self.model)
  28         self.set_rules_hint(True)
  29 
  30         # Icon column
  31         image_cell = Gtk.CellRendererPixbuf()
  32         column = Gtk.TreeViewColumn("", image_cell, pixbuf=COL_ICON)
  33         column.set_reorderable(True)
  34         column.set_sort_indicator(False)
  35         self.append_column(column)
  36 
  37         # Text columns
  38         default_text_cell = self.set_text_cell()
  39         name_cell = self.set_text_cell()
  40         name_cell.set_padding(5, 0)
  41 
  42         self.set_column(name_cell, _("Name"), COL_NAME, 200)
  43         self.set_column(default_text_cell, _("Year"), COL_YEAR, 60)
  44         self.set_column(default_text_cell, _("Runner"), COL_RUNNER_HUMAN_NAME, 120)
  45         self.set_column(default_text_cell, _("Platform"), COL_PLATFORM, 120)
  46         self.set_column(default_text_cell, _("Last Played"), COL_LASTPLAYED_TEXT, 120)
  47         self.set_sort_with_column(COL_LASTPLAYED_TEXT, COL_LASTPLAYED)
  48         self.set_column(default_text_cell, _("Installed At"), COL_INSTALLED_AT_TEXT, 120)
  49         self.set_sort_with_column(COL_INSTALLED_AT_TEXT, COL_INSTALLED_AT)
  50         self.set_column(default_text_cell, _("Play Time"), COL_PLAYTIME_TEXT, 100)
  51         self.set_sort_with_column(COL_PLAYTIME_TEXT, COL_PLAYTIME)
  52 
  53         self.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
  54 
  55         self.connect_signals()
  56         self.connect("row-activated", self.on_row_activated)
  57         self.get_selection().connect("changed", self.on_cursor_changed)
  58 
  59     @staticmethod
  60     def set_text_cell():
  61         text_cell = Gtk.CellRendererText()
  62         text_cell.set_padding(10, 0)
  63         text_cell.set_property("ellipsize", Pango.EllipsizeMode.END)
  64         return text_cell
  65 
  66     def set_column(self, cell, header, column_id, default_width, sort_id=None):
  67         column = Gtk.TreeViewColumn(header, cell, markup=column_id)
  68         column.set_sort_indicator(True)
  69         column.set_sort_column_id(column_id if sort_id is None else sort_id)
  70         self.set_column_sort(column_id if sort_id is None else sort_id)
  71         column.set_resizable(True)
  72         column.set_reorderable(True)
  73         width = settings.read_setting("%s_column_width" % COLUMN_NAMES[column_id], "list view")
  74         column.set_fixed_width(int(width) if width else default_width)
  75         self.append_column(column)
  76         column.connect("notify::width", self.on_column_width_changed)
  77         return column
  78 
  79     def set_column_sort(self, col):
  80         """Sort a column and fallback to sorting by name and runner."""
  81         self.model.set_sort_func(col, sort_func, col)
  82 
  83     def set_sort_with_column(self, col, sort_col):
  84         """Sort a column by using another column's data"""
  85         self.model.set_sort_func(col, sort_func, sort_col)
  86 
  87     def get_selected_item(self):
  88         """Return the currently selected game's id."""
  89         selection = self.get_selection()
  90         if not selection:
  91             return None
  92         _model, select_iter = selection.get_selected()
  93         if select_iter:
  94             return select_iter
  95 
  96     def select(self):
  97         self.set_cursor(self.current_path[0])
  98 
  99     def set_selected_game(self, game_id):
  100         row = self.game_store.get_row_by_id(game_id, filtered=True)
  101         if row:
  102             self.set_cursor(row.path)
  103 
  104     def on_row_activated(self, widget, line=None, column=None):
  105         """Handles double clicks"""
  106         selected_item = self.get_selected_item()
  107         if selected_item:
  108             selected_game = self.get_selected_game(selected_item)
  109         else:
  110             selected_game = None
  111         self.emit("game-activated", selected_game)
  112 
  113     def on_cursor_changed(self, widget, _line=None, _column=None):
  114         selected_item = self.get_selected_item()
  115         if selected_item:
  116             self.selected_game = self.get_selected_game(selected_item)
  117         else:
  118             self.selected_game = None
  119         self.emit("game-selected", self.selected_game)
  120 
  121     @staticmethod
  122     def on_column_width_changed(col, *args):
  123         col_name = col.get_title()
  124         if col_name:
  125             settings.write_setting(
  126                 col_name.replace(" ", "") + "_column_width",
  127                 col.get_fixed_width(),
  128                 "list view",
  129             )
  130
  [end of lutris/gui/views/list.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/lutris/gui/views/list.py b/lutris/gui/views/list.py
  --- a/lutris/gui/views/list.py
  +++ b/lutris/gui/views/list.py
  @@ -39,7 +39,7 @@
           name_cell = self.set_text_cell()
           name_cell.set_padding(5, 0)
   
  -        self.set_column(name_cell, _("Name"), COL_NAME, 200)
  +        self.set_column(name_cell, _("Name"), COL_NAME, 200, always_visible=True)
           self.set_column(default_text_cell, _("Year"), COL_YEAR, 60)
           self.set_column(default_text_cell, _("Runner"), COL_RUNNER_HUMAN_NAME, 120)
           self.set_column(default_text_cell, _("Platform"), COL_PLATFORM, 120)
  @@ -63,7 +63,7 @@
           text_cell.set_property("ellipsize", Pango.EllipsizeMode.END)
           return text_cell
   
  -    def set_column(self, cell, header, column_id, default_width, sort_id=None):
  +    def set_column(self, cell, header, column_id, default_width, always_visible=False, sort_id=None):
           column = Gtk.TreeViewColumn(header, cell, markup=column_id)
           column.set_sort_indicator(True)
           column.set_sort_column_id(column_id if sort_id is None else sort_id)
  @@ -71,9 +71,12 @@
           column.set_resizable(True)
           column.set_reorderable(True)
           width = settings.read_setting("%s_column_width" % COLUMN_NAMES[column_id], "list view")
  +        is_visible = settings.read_setting("%s_visible" % COLUMN_NAMES[column_id], "list view")
           column.set_fixed_width(int(width) if width else default_width)
  +        column.set_visible(is_visible == "True" or always_visible if is_visible else True)
           self.append_column(column)
           column.connect("notify::width", self.on_column_width_changed)
  +        column.get_button().connect('button-press-event', self.on_column_header_button_pressed)
           return column
   
       def set_column_sort(self, col):
  @@ -101,6 +104,13 @@
           if row:
               self.set_cursor(row.path)
   
  +    def on_column_header_button_pressed(self, button, event):
  +        """Handles column header button press events"""
  +        if event.button == 3:
  +            menu = GameListColumnToggleMenu(self.get_columns())
  +            menu.popup_at_pointer(None)
  +            return True
  +
       def on_row_activated(self, widget, line=None, column=None):
           """Handles double clicks"""
           selected_item = self.get_selected_item()
  @@ -127,3 +137,37 @@
                   col.get_fixed_width(),
                   "list view",
               )
  +
  +
  +class GameListColumnToggleMenu(Gtk.Menu):
  +
  +    def __init__(self, columns):
  +        super().__init__()
  +        self.columns = columns
  +        self.column_map = {}
  +        self.create_menuitems()
  +        self.show_all()
  +
  +    def create_menuitems(self):
  +        for column in self.columns:
  +            title = column.get_title()
  +            if title == "":
  +                continue
  +            checkbox = Gtk.CheckMenuItem(title)
  +            checkbox.set_active(column.get_visible())
  +            if title == _("Name"):
  +                checkbox.set_sensitive(False)
  +            else:
  +                checkbox.connect("toggled", self.on_toggle_column)
  +            self.column_map[checkbox] = column
  +            self.append(checkbox)
  +
  +    def on_toggle_column(self, check_menu_item):
  +        column = self.column_map[check_menu_item]
  +        is_visible = check_menu_item.get_active()
  +        column.set_visible(is_visible)
  +        settings.write_setting(
  +            column.get_title().replace(" ", "") + "_visible",
  +            str(is_visible),
  +            "list view",
  +        )
  
verification_info: {"golden_diff": "diff --git a/lutris/gui/views/list.py b/lutris/gui/views/list.py\n--- a/lutris/gui/views/list.py\n+++ b/lutris/gui/views/list.py\n@@ -39,7 +39,7 @@\n         name_cell = self.set_text_cell()\n         name_cell.set_padding(5, 0)\n \n-        self.set_column(name_cell, _(\"Name\"), COL_NAME, 200)\n+        self.set_column(name_cell, _(\"Name\"), COL_NAME, 200, always_visible=True)\n         self.set_column(default_text_cell, _(\"Year\"), COL_YEAR, 60)\n         self.set_column(default_text_cell, _(\"Runner\"), COL_RUNNER_HUMAN_NAME, 120)\n         self.set_column(default_text_cell, _(\"Platform\"), COL_PLATFORM, 120)\n@@ -63,7 +63,7 @@\n         text_cell.set_property(\"ellipsize\", Pango.EllipsizeMode.END)\n         return text_cell\n \n-    def set_column(self, cell, header, column_id, default_width, sort_id=None):\n+    def set_column(self, cell, header, column_id, default_width, always_visible=False, sort_id=None):\n         column = Gtk.TreeViewColumn(header, cell, markup=column_id)\n         column.set_sort_indicator(True)\n         column.set_sort_column_id(column_id if sort_id is None else sort_id)\n@@ -71,9 +71,12 @@\n         column.set_resizable(True)\n         column.set_reorderable(True)\n         width = settings.read_setting(\"%s_column_width\" % COLUMN_NAMES[column_id], \"list view\")\n+        is_visible = settings.read_setting(\"%s_visible\" % COLUMN_NAMES[column_id], \"list view\")\n         column.set_fixed_width(int(width) if width else default_width)\n+        column.set_visible(is_visible == \"True\" or always_visible if is_visible else True)\n         self.append_column(column)\n         column.connect(\"notify::width\", self.on_column_width_changed)\n+        column.get_button().connect('button-press-event', self.on_column_header_button_pressed)\n         return column\n \n     def set_column_sort(self, col):\n@@ -101,6 +104,13 @@\n         if row:\n             self.set_cursor(row.path)\n \n+    def on_column_header_button_pressed(self, button, event):\n+        \"\"\"Handles column header button press events\"\"\"\n+        if event.button == 3:\n+            menu = GameListColumnToggleMenu(self.get_columns())\n+            menu.popup_at_pointer(None)\n+            return True\n+\n     def on_row_activated(self, widget, line=None, column=None):\n         \"\"\"Handles double clicks\"\"\"\n         selected_item = self.get_selected_item()\n@@ -127,3 +137,37 @@\n                 col.get_fixed_width(),\n                 \"list view\",\n             )\n+\n+\n+class GameListColumnToggleMenu(Gtk.Menu):\n+\n+    def __init__(self, columns):\n+        super().__init__()\n+        self.columns = columns\n+        self.column_map = {}\n+        self.create_menuitems()\n+        self.show_all()\n+\n+    def create_menuitems(self):\n+        for column in self.columns:\n+            title = column.get_title()\n+            if title == \"\":\n+                continue\n+            checkbox = Gtk.CheckMenuItem(title)\n+            checkbox.set_active(column.get_visible())\n+            if title == _(\"Name\"):\n+                checkbox.set_sensitive(False)\n+            else:\n+                checkbox.connect(\"toggled\", self.on_toggle_column)\n+            self.column_map[checkbox] = column\n+            self.append(checkbox)\n+\n+    def on_toggle_column(self, check_menu_item):\n+        column = self.column_map[check_menu_item]\n+        is_visible = check_menu_item.get_active()\n+        column.set_visible(is_visible)\n+        settings.write_setting(\n+            column.get_title().replace(\" \", \"\") + \"_visible\",\n+            str(is_visible),\n+            \"list view\",\n+        )\n", "issue": "Add option to turn columns on/off in List View\nWhen right-clicking to table headbar in List View, you expect to get a menu for turning columns on/off, but you just select first game in the list.\n", "before_files": [{"content": "\"\"\"TreeView based game list\"\"\"\nfrom gettext import gettext as _\n\n# Third Party Libraries\n# pylint: disable=no-member\nfrom gi.repository import Gtk, Pango\n\n# Lutris Modules\nfrom lutris import settings\nfrom lutris.gui.views import (\n    COL_ICON, COL_INSTALLED_AT, COL_INSTALLED_AT_TEXT, COL_LASTPLAYED, COL_LASTPLAYED_TEXT, COL_NAME, COL_PLATFORM,\n    COL_PLAYTIME, COL_PLAYTIME_TEXT, COL_RUNNER_HUMAN_NAME, COL_YEAR, COLUMN_NAMES\n)\nfrom lutris.gui.views.base import GameView\nfrom lutris.gui.views.store import sort_func\n\n\nclass GameListView(Gtk.TreeView, GameView):\n\n    \"\"\"Show the main list of games.\"\"\"\n\n    __gsignals__ = GameView.__gsignals__\n\n    def __init__(self, store):\n        self.game_store = store\n        self.model = self.game_store.modelsort\n        super().__init__(self.model)\n        self.set_rules_hint(True)\n\n        # Icon column\n        image_cell = Gtk.CellRendererPixbuf()\n        column = Gtk.TreeViewColumn(\"\", image_cell, pixbuf=COL_ICON)\n        column.set_reorderable(True)\n        column.set_sort_indicator(False)\n        self.append_column(column)\n\n        # Text columns\n        default_text_cell = self.set_text_cell()\n        name_cell = self.set_text_cell()\n        name_cell.set_padding(5, 0)\n\n        self.set_column(name_cell, _(\"Name\"), COL_NAME, 200)\n        self.set_column(default_text_cell, _(\"Year\"), COL_YEAR, 60)\n        self.set_column(default_text_cell, _(\"Runner\"), COL_RUNNER_HUMAN_NAME, 120)\n        self.set_column(default_text_cell, _(\"Platform\"), COL_PLATFORM, 120)\n        self.set_column(default_text_cell, _(\"Last Played\"), COL_LASTPLAYED_TEXT, 120)\n        self.set_sort_with_column(COL_LASTPLAYED_TEXT, COL_LASTPLAYED)\n        self.set_column(default_text_cell, _(\"Installed At\"), COL_INSTALLED_AT_TEXT, 120)\n        self.set_sort_with_column(COL_INSTALLED_AT_TEXT, COL_INSTALLED_AT)\n        self.set_column(default_text_cell, _(\"Play Time\"), COL_PLAYTIME_TEXT, 100)\n        self.set_sort_with_column(COL_PLAYTIME_TEXT, COL_PLAYTIME)\n\n        self.get_selection().set_mode(Gtk.SelectionMode.SINGLE)\n\n        self.connect_signals()\n        self.connect(\"row-activated\", self.on_row_activated)\n        self.get_selection().connect(\"changed\", self.on_cursor_changed)\n\n    @staticmethod\n    def set_text_cell():\n        text_cell = Gtk.CellRendererText()\n        text_cell.set_padding(10, 0)\n        text_cell.set_property(\"ellipsize\", Pango.EllipsizeMode.END)\n        return text_cell\n\n    def set_column(self, cell, header, column_id, default_width, sort_id=None):\n        column = Gtk.TreeViewColumn(header, cell, markup=column_id)\n        column.set_sort_indicator(True)\n        column.set_sort_column_id(column_id if sort_id is None else sort_id)\n        self.set_column_sort(column_id if sort_id is None else sort_id)\n        column.set_resizable(True)\n        column.set_reorderable(True)\n        width = settings.read_setting(\"%s_column_width\" % COLUMN_NAMES[column_id], \"list view\")\n        column.set_fixed_width(int(width) if width else default_width)\n        self.append_column(column)\n        column.connect(\"notify::width\", self.on_column_width_changed)\n        return column\n\n    def set_column_sort(self, col):\n        \"\"\"Sort a column and fallback to sorting by name and runner.\"\"\"\n        self.model.set_sort_func(col, sort_func, col)\n\n    def set_sort_with_column(self, col, sort_col):\n        \"\"\"Sort a column by using another column's data\"\"\"\n        self.model.set_sort_func(col, sort_func, sort_col)\n\n    def get_selected_item(self):\n        \"\"\"Return the currently selected game's id.\"\"\"\n        selection = self.get_selection()\n        if not selection:\n            return None\n        _model, select_iter = selection.get_selected()\n        if select_iter:\n            return select_iter\n\n    def select(self):\n        self.set_cursor(self.current_path[0])\n\n    def set_selected_game(self, game_id):\n        row = self.game_store.get_row_by_id(game_id, filtered=True)\n        if row:\n            self.set_cursor(row.path)\n\n    def on_row_activated(self, widget, line=None, column=None):\n        \"\"\"Handles double clicks\"\"\"\n        selected_item = self.get_selected_item()\n        if selected_item:\n            selected_game = self.get_selected_game(selected_item)\n        else:\n            selected_game = None\n        self.emit(\"game-activated\", selected_game)\n\n    def on_cursor_changed(self, widget, _line=None, _column=None):\n        selected_item = self.get_selected_item()\n        if selected_item:\n            self.selected_game = self.get_selected_game(selected_item)\n        else:\n            self.selected_game = None\n        self.emit(\"game-selected\", self.selected_game)\n\n    @staticmethod\n    def on_column_width_changed(col, *args):\n        col_name = col.get_title()\n        if col_name:\n            settings.write_setting(\n                col_name.replace(\" \", \"\") + \"_column_width\",\n                col.get_fixed_width(),\n                \"list view\",\n            )\n", "path": "lutris/gui/views/list.py"}]}