1 """Provides quick switcher"""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
5 from qtpy
import QtCore
7 from qtpy
.QtCore
import Qt
8 from qtpy
.QtCore
import Signal
10 from .. import qtutils
11 from ..widgets
import defs
12 from ..widgets
import standard
13 from ..widgets
import text
16 def switcher_inner_view(
17 context
, entries
, title
=None, place_holder
=None, enter_action
=None, parent
=None
19 dialog
= SwitcherInnerView(
20 context
, entries
, title
, place_holder
, enter_action
, parent
26 def switcher_outer_view(context
, entries
, place_holder
=None, parent
=None):
27 dialog
= SwitcherOuterView(context
, entries
, place_holder
, parent
)
32 def switcher_item(key
, icon
=None, name
=None):
33 return SwitcherListItem(key
, icon
, name
)
37 selection_move_keys
= [
47 return selection_move_keys
50 class Switcher(standard
.Dialog
):
52 Quick switcher base class. This contains input field, filter proxy model.
53 This will be inherited by outer-view class(SwitcherOuterView) or inner-view class
56 inner-view class is a quick-switcher widget including view. In this case, this
57 switcher will have switcher_list field, and show the items list in itself.
58 outer-view class is a quick-switcher widget without view(only input field), which
59 means sharing model with other view class.
61 switcher_selection_move signal is for the event that selection move key like UP,
62 DOWN has pressed while focusing on input field.
72 standard
.Dialog
.__init
__(self
, parent
=parent
)
74 self
.context
= context
75 self
.entries_model
= entries_model
77 self
.filter_input
= SwitcherLineEdit(place_holder
=place_holder
, parent
=self
)
79 self
.proxy_model
= SwitcherSortFilterProxyModel(entries_model
, parent
=self
)
80 self
.switcher_list
= None
82 self
.filter_input
.textChanged
.connect(self
.filter_input_changed
)
84 def filter_input_changed(self
):
85 input_text
= self
.filter_input
.text()
86 pattern
= '.*'.join(re
.escape(c
) for c
in input_text
)
87 self
.proxy_model
.setFilterRegExp(pattern
)
90 class SwitcherInnerView(Switcher
):
104 place_holder
=place_holder
,
107 self
.setWindowTitle(title
)
108 if parent
is not None:
109 self
.setWindowModality(Qt
.WindowModal
)
111 self
.enter_action
= enter_action
112 self
.switcher_list
= SwitcherTreeView(
113 self
.proxy_model
, self
.enter_selected_item
, parent
=self
116 self
.main_layout
= qtutils
.vbox(
117 defs
.no_margin
, defs
.spacing
, self
.filter_input
, self
.switcher_list
119 self
.setLayout(self
.main_layout
)
121 # moving key has pressed while focusing on input field
122 self
.filter_input
.switcher_selection_move
.connect(
123 self
.switcher_list
.keyPressEvent
125 # escape key pressed while focusing on input field
126 self
.filter_input
.switcher_escape
.connect(self
.close
)
127 self
.filter_input
.switcher_accept
.connect(self
.accept_selected_item
)
128 # some key except moving key has pressed while focusing on list view
129 self
.switcher_list
.switcher_inner_text
.connect(self
.filter_input
.keyPressEvent
)
131 # default selection for first index
132 first_proxy_idx
= self
.proxy_model
.index(0, 0)
133 self
.switcher_list
.setCurrentIndex(first_proxy_idx
)
135 self
.set_initial_geometry(parent
)
137 def accept_selected_item(self
):
138 item
= self
.switcher_list
.selected_item()
140 self
.enter_action(item
)
143 def set_initial_geometry(self
, parent
):
144 """Set the initial size and position"""
148 width
= parent
.width()
149 height
= parent
.height()
150 self
.resize(max(width
* 2 // 3, 320), max(height
* 2 // 3, 240))
152 def enter_selected_item(self
, index
):
153 item
= self
.switcher_list
.model().itemFromIndex(index
)
155 self
.enter_action(item
)
159 class SwitcherOuterView(Switcher
):
160 def __init__(self
, context
, entries_model
, place_holder
=None, parent
=None):
165 place_holder
=place_holder
,
168 self
.filter_input
.hide()
170 self
.main_layout
= qtutils
.vbox(defs
.no_margin
, defs
.spacing
, self
.filter_input
)
171 self
.setLayout(self
.main_layout
)
173 def filter_input_changed(self
):
174 super().filter_input_changed()
175 # Hide the input when it becomes empty.
176 input_text
= self
.filter_input
.text()
178 self
.filter_input
.hide()
181 class SwitcherLineEdit(text
.LineEdit
):
182 """Quick switcher input line class"""
184 # signal is for the event that selection move key like UP, DOWN has pressed
185 # while focusing on this line edit widget
186 switcher_selection_move
= Signal(QtGui
.QKeyEvent
)
187 switcher_visible
= Signal(bool)
188 switcher_accept
= Signal()
189 switcher_escape
= Signal()
191 def __init__(self
, place_holder
=None, parent
=None):
192 text
.LineEdit
.__init
__(self
, parent
=parent
)
194 self
.setPlaceholderText(place_holder
)
196 def keyPressEvent(self
, event
):
198 To be able to move the selection while focus on the input field, input text
199 field should be able to filter pressed key.
200 If pressed key is moving selection key like UP or DOWN, the
201 switcher_selection_move signal will be emitted and the view selection
202 will be moved regardless whether Switcher is inner-view or outer-view.
203 Or else, simply act like text input to the field.
205 selection_moving_keys
= moving_keys()
206 pressed_key
= event
.key()
208 if pressed_key
== Qt
.Key_Escape
:
209 self
.switcher_escape
.emit()
210 elif pressed_key
in (Qt
.Key_Enter
, Qt
.Key_Return
):
211 self
.switcher_accept
.emit()
212 elif pressed_key
in selection_moving_keys
:
213 self
.switcher_selection_move
.emit(event
)
215 super().keyPressEvent(event
)
218 class SwitcherSortFilterProxyModel(QtCore
.QSortFilterProxyModel
):
219 """Filtering class for candidate items."""
221 def __init__(self
, entries
, parent
=None):
222 QtCore
.QSortFilterProxyModel
.__init
__(self
, parent
)
224 self
.entries
= entries
226 self
.setDynamicSortFilter(True)
227 self
.setSourceModel(entries
)
228 self
.setFilterCaseSensitivity(Qt
.CaseInsensitive
)
230 def itemFromIndex(self
, index
):
231 return self
.entries
.itemFromIndex(self
.mapToSource(index
))
234 # pylint: disable=too-many-ancestors
235 class SwitcherTreeView(standard
.TreeView
):
236 """Tree view class for showing proxy items in SwitcherSortFilterProxyModel"""
238 # signal is for the event that some key except moving key has pressed
239 # while focusing this view
240 switcher_inner_text
= Signal(QtGui
.QKeyEvent
)
242 def __init__(self
, entries_proxy_model
, enter_action
, parent
=None):
243 standard
.TreeView
.__init
__(self
, parent
)
245 self
.setHeaderHidden(True)
246 self
.setModel(entries_proxy_model
)
248 self
.doubleClicked
.connect(enter_action
)
250 def keyPressEvent(self
, event
):
251 """hooks when a key has pressed while focusing on list view"""
252 selection_moving_keys
= moving_keys()
253 pressed_key
= event
.key()
255 if pressed_key
in selection_moving_keys
or pressed_key
== Qt
.Key_Escape
:
256 super().keyPressEvent(event
)
258 self
.switcher_inner_text
.emit(event
)
261 class SwitcherListItem(QtGui
.QStandardItem
):
262 """Item class for SwitcherTreeView and SwitcherSortFilterProxyModel"""
264 def __init__(self
, key
, icon
=None, name
=None):
265 QtGui
.QStandardItem
.__init
__(self
)