widgets.remote: retain focus when showing remote messages
[git-cola.git] / cola / widgets / finder.py
blobab74e8b2226171a8405ced92922790865bf930ea
1 """File finder widgets"""
2 import os
3 from functools import partial
5 from qtpy import QtCore
6 from qtpy import QtWidgets
7 from qtpy.QtCore import Qt
8 from qtpy.QtCore import Signal
10 from ..i18n import N_
11 from ..qtutils import get
12 from ..utils import Group
13 from .. import cmds
14 from .. import core
15 from .. import gitcmds
16 from .. import hotkeys
17 from .. import icons
18 from .. import utils
19 from .. import qtutils
20 from . import completion
21 from . import defs
22 from . import filetree
23 from . import standard
24 from . import text
27 def finder(context, paths=None):
28 """Prompt and use 'git grep' to find the content."""
29 parent = qtutils.active_window()
30 widget = new_finder(context, paths=paths, parent=parent)
31 widget.show()
32 widget.raise_()
33 return widget
36 def new_finder(context, paths=None, parent=None):
37 """Create a finder widget"""
38 widget = Finder(context, parent=parent)
39 widget.search_for(paths or '')
40 return widget
43 def add_wildcards(arg):
44 """Add "*" around user input to generate ls-files pathspecs matches
46 >>> '*x*' == \
47 add_wildcards('x') == \
48 add_wildcards('*x') == \
49 add_wildcards('x*') == \
50 add_wildcards('*x*')
51 True
53 """
54 if not arg.startswith('*'):
55 arg = '*' + arg
56 if not arg.endswith('*'):
57 arg = arg + '*'
58 return arg
61 def show_help(context):
62 """Show the help page"""
63 help_text = N_(
64 """
65 Keyboard Shortcuts
66 ------------------
67 J, Down = Move Down
68 K, Up = Move Up
69 Enter = Edit Selected Files
70 Spacebar = Open File Using Default Application
71 Ctrl + L = Focus Text Entry Field
72 ? = Show Help
74 The up and down arrows change focus between the text entry field
75 and the results.
76 """
78 title = N_('Help - Find Files')
79 return text.text_dialog(context, help_text, title)
82 class FindFilesThread(QtCore.QThread):
83 """Finds files asynchronously"""
85 result = Signal(object)
87 def __init__(self, context, parent):
88 QtCore.QThread.__init__(self, parent)
89 self.context = context
90 self.query = None
92 def run(self):
93 context = self.context
94 query = self.query
95 if query is None:
96 args = []
97 else:
98 args = [add_wildcards(arg) for arg in utils.shell_split(query)]
99 filenames = gitcmds.tracked_files(context, *args)
100 if query == self.query:
101 self.result.emit(filenames)
102 else:
103 self.run()
106 class Finder(standard.Dialog):
107 """File Finder dialog"""
109 def __init__(self, context, parent=None):
110 standard.Dialog.__init__(self, parent)
111 self.context = context
112 self.setWindowTitle(N_('Find Files'))
113 if parent is not None:
114 self.setWindowModality(Qt.WindowModal)
116 label = os.path.basename(core.getcwd()) + '/'
117 self.input_label = QtWidgets.QLabel(label)
118 self.input_txt = completion.GitTrackedLineEdit(context, hint=N_('<path> ...'))
120 self.tree = filetree.FileTree(parent=self)
122 self.edit_button = qtutils.edit_button(default=True)
123 self.edit_button.setShortcut(hotkeys.EDIT)
125 name = cmds.OpenDefaultApp.name()
126 icon = icons.default_app()
127 self.open_default_button = qtutils.create_button(text=name, icon=icon)
128 self.open_default_button.setShortcut(hotkeys.PRIMARY_ACTION)
130 self.button_group = Group(self.edit_button, self.open_default_button)
131 self.button_group.setEnabled(False)
133 self.refresh_button = qtutils.refresh_button()
134 self.refresh_button.setShortcut(hotkeys.REFRESH)
136 self.help_button = qtutils.create_button(
137 text=N_('Help'), tooltip=N_('Show help\nShortcut: ?'), icon=icons.question()
140 self.close_button = qtutils.close_button()
142 self.input_layout = qtutils.hbox(
143 defs.no_margin, defs.button_spacing, self.input_label, self.input_txt
146 self.bottom_layout = qtutils.hbox(
147 defs.no_margin,
148 defs.button_spacing,
149 self.help_button,
150 self.refresh_button,
151 qtutils.STRETCH,
152 self.close_button,
153 self.open_default_button,
154 self.edit_button,
157 self.main_layout = qtutils.vbox(
158 defs.margin,
159 defs.no_spacing,
160 self.input_layout,
161 self.tree,
162 self.bottom_layout,
164 self.setLayout(self.main_layout)
165 self.setFocusProxy(self.input_txt)
167 thread = self.worker_thread = FindFilesThread(context, self)
168 thread.result.connect(self.process_result, type=Qt.QueuedConnection)
170 # pylint: disable=no-member
171 self.input_txt.textChanged.connect(lambda s: self.search())
172 self.input_txt.activated.connect(self.focus_tree)
173 self.input_txt.down.connect(self.focus_tree)
174 self.input_txt.enter.connect(self.focus_tree)
176 item_selection_changed = self.tree_item_selection_changed
177 self.tree.itemSelectionChanged.connect(item_selection_changed)
178 self.tree.up.connect(self.focus_input)
179 self.tree.space.connect(self.open_default)
181 qtutils.add_action(
182 self, 'Focus Input', self.focus_input, hotkeys.FOCUS, hotkeys.FINDER
185 self.show_help_action = qtutils.add_action(
186 self, N_('Show Help'), partial(show_help, context), hotkeys.QUESTION
189 qtutils.connect_button(self.edit_button, self.edit)
190 qtutils.connect_button(self.open_default_button, self.open_default)
191 qtutils.connect_button(self.refresh_button, self.search)
192 qtutils.connect_button(self.help_button, partial(show_help, context))
193 qtutils.connect_button(self.close_button, self.close)
194 qtutils.add_close_action(self)
196 self.init_size(parent=parent)
198 def focus_tree(self):
199 self.tree.setFocus()
201 def focus_input(self):
202 self.input_txt.setFocus()
204 def search(self):
205 self.button_group.setEnabled(False)
206 self.refresh_button.setEnabled(False)
207 query = get(self.input_txt)
208 self.worker_thread.query = query
209 self.worker_thread.start()
211 def search_for(self, txt):
212 self.input_txt.set_value(txt)
213 self.focus_input()
215 def process_result(self, filenames):
216 self.tree.set_filenames(filenames, select=True)
217 self.refresh_button.setEnabled(True)
219 def edit(self):
220 context = self.context
221 paths = self.tree.selected_filenames()
222 cmds.do(cmds.Edit, context, paths, background_editor=True)
224 def open_default(self):
225 context = self.context
226 paths = self.tree.selected_filenames()
227 cmds.do(cmds.OpenDefaultApp, context, paths)
229 def tree_item_selection_changed(self):
230 enabled = bool(self.tree.selected_item())
231 self.button_group.setEnabled(enabled)