summaryrefslogtreecommitdiff
path: root/virtManager/oslist.py
blob: 43d1d2731b36ecc69b36ca2f25f2432aa9f8f65b (plain)
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# Copyright (C) 2018 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

from gi.repository import Gdk, Gtk

import virtinst
from virtinst import xmlutil

from .baseclass import vmmGObjectUI


def _always_show(osobj):
    return bool(osobj.is_generic() or osobj.is_linux_generic())


class vmmOSList(vmmGObjectUI):
    __gsignals__ = {
        "os-selected": (vmmGObjectUI.RUN_FIRST, None, [object])
    }

    def __init__(self):
        vmmGObjectUI.__init__(self, "oslist.ui", "vmm-oslist")
        self._cleanup_on_app_close()

        self._filter_name = None
        self._filter_eol = True
        self._selected_os = None
        self.search_entry = self.widget("os-name")
        self.search_entry.set_placeholder_text(_("Type to start searching..."))
        self.eol_text = self.widget("eol-warn").get_text()

        self.builder.connect_signals({
            "on_include_eol_toggled": self._eol_toggled_cb,

            "on_os_name_activate": self._entry_activate_cb,
            "on_os_name_key_press_event": self._key_press_cb,
            "on_os_name_search_changed": self._search_changed_cb,
            "on_os_name_stop_search": self._stop_search_cb,
            "on_os_list_row_activated": self._os_selected_cb,
        })

        self._init_state()

    def _cleanup(self):
        pass


    ###########
    # UI init #
    ###########

    def _init_state(self):
        os_list = self.widget("os-list")

        # (os object, label)
        os_list_model = Gtk.ListStore(object, str)

        all_os = virtinst.OSDB.list_os(sortkey="label")
        # Always put the generic entries at the end of the list
        all_os = list(sorted(all_os, key=_always_show))

        for os in all_os:
            os_list_model.append([os, "%s (%s)" % (os.label, os.name)])

        model_filter = Gtk.TreeModelFilter(child_model=os_list_model)
        model_filter.set_visible_func(self._filter_os_cb)

        os_list.set_model(model_filter)

        nameCol = Gtk.TreeViewColumn(_("Name"))
        nameCol.set_spacing(6)

        text = Gtk.CellRendererText()
        nameCol.pack_start(text, True)
        nameCol.add_attribute(text, 'text', 1)
        os_list.append_column(nameCol)

        markup = "<small>%s</small>" % xmlutil.xml_escape(
                self.widget("eol-warn").get_text())
        self.widget("eol-warn").set_markup(markup)


    ###################
    # Private helpers #
    ###################

    def _set_default_selection(self):
        os_list = self.widget("os-list")
        sel = os_list.get_selection()
        if not self.is_visible():
            return
        if not len(os_list.get_model()):
            return   # pragma: no cover
        sel.select_iter(os_list.get_model()[0].iter)

    def _refilter(self):
        os_list = self.widget("os-list")
        sel = os_list.get_selection()
        sel.unselect_all()
        os_list.get_model().refilter()
        self._set_default_selection()

    def _filter_by_name(self, partial_name):
        self._filter_name = partial_name.lower()
        self._refilter()

    def _clear_filter(self):
        self._filter_by_name("")
        self.widget("os-scroll").get_vadjustment().set_value(0)

    def _sync_os_selection(self):
        model, titer = self.widget("os-list").get_selection().get_selected()
        self._selected_os = None
        if titer:
            self._selected_os = model[titer][0]
            self.search_entry.set_text(self._selected_os.label)

        self.emit("os-selected", self._selected_os)

    def _show_popover(self):
        # Match width to the search_entry width. Height is based on
        # whatever we can fit into the hardcoded create wizard sizes
        r = self.search_entry.get_allocation()
        self.topwin.set_size_request(r.width, 350)

        self.topwin.set_relative_to(self.search_entry)
        self.topwin.popup()
        self._set_default_selection()


    ################
    # UI Callbacks #
    ################

    def _entry_activate_cb(self, src):
        os_list = self.widget("os-list")
        if not os_list.is_visible():
            return  # pragma: no cover

        sel = os_list.get_selection()
        model, rows = sel.get_selected_rows()
        if rows:
            self.select_os(model[rows[0]][0])

    def _key_press_cb(self, src, event):
        if Gdk.keyval_name(event.keyval) != "Down":
            return
        self._show_popover()
        self.widget("os-list").grab_focus()

    def _eol_toggled_cb(self, src):
        self._filter_eol = not src.get_active()
        self._refilter()

    def _search_changed_cb(self, src):
        """
        Called text in search_entry is changed
        """
        searchname = src.get_text().strip()
        selected_label = None
        if self._selected_os:
            selected_label = self._selected_os.label

        if (not src.get_sensitive() or
            not searchname or
            selected_label == searchname):
            self.topwin.popdown()
            self._clear_filter()
            return

        self._filter_by_name(searchname)
        self._show_popover()

    def _stop_search_cb(self, src):
        """
        Called when the search window is closed, like with Escape key
        """
        if self._selected_os:
            self.search_entry.set_text(self._selected_os.label)
        else:
            self.search_entry.set_text("")

    def _os_selected_cb(self, src,  path, column):
        self._sync_os_selection()

    def _filter_os_cb(self, model, titer, ignore1):
        osobj = model.get(titer, 0)[0]
        if self._filter_eol:
            if osobj.eol:
                return False

        if _always_show(osobj):
            return True

        if self._filter_name is not None and self._filter_name != "":
            label = osobj.label.lower()
            name = osobj.name.lower()
            if (label.find(self._filter_name) == -1 and
                name.find(self._filter_name) == -1):
                return False

        return True


    ###############
    # Public APIs #
    ###############

    def reset_state(self):
        self._selected_os = None
        self.search_entry.set_text("")
        self._clear_filter()
        self._sync_os_selection()

    def select_os(self, vmosobj):
        self._clear_filter()

        os_list = self.widget("os-list")
        if vmosobj.eol and not self.widget("include-eol").get_active():
            self.widget("include-eol").set_active(True)

        for row in os_list.get_model():
            osobj = row[0]
            if osobj.name != vmosobj.name:
                continue

            os_list.get_selection().select_iter(row.iter)
            self._sync_os_selection()
            return

    def get_selected_os(self):
        return self._selected_os

    def set_sensitive(self, sensitive):
        if sensitive == self.search_entry.get_sensitive():
            return

        if not sensitive:
            self.search_entry.set_sensitive(False)
            self.reset_state()
        else:
            if self._selected_os:
                self.select_os(self._selected_os)
            else:
                self.reset_state()
            self.search_entry.set_sensitive(True)