Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 

 

# Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org> 

# 

# This file is part of qutebrowser. 

# 

# qutebrowser is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# qutebrowser is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. 

 

# Because every method needs to have a log_stack argument 

# pylint: disable=unused-argument 

 

"""Message singleton so we don't have to define unneeded signals.""" 

 

import traceback 

 

from PyQt5.QtCore import pyqtSignal, QObject 

 

from qutebrowser.utils import usertypes, log, utils 

 

 

def _log_stack(typ, stack): 

"""Log the given message stacktrace. 

 

Args: 

typ: The type of the message (str) 

stack: The stack as an iterable of strings or a single string 

""" 

try: 

# traceback.format_exc() produces a list of strings, while 

# traceback.format_stack() produces a single string... 

stack = stack.splitlines() 

except AttributeError: 

pass 

stack_text = '\n'.join(line.rstrip() for line in stack) 

log.message.debug("Stack for {} message:\n{}".format(typ, stack_text)) 

 

 

def error(message, *, stack=None, replace=False): 

"""Convenience function to display an error message in the statusbar. 

 

Args: 

message: The message to show 

stack: The stack trace to show. 

replace: Replace existing messages with replace=True 

""" 

57 ↛ 61line 57 didn't jump to line 61, because the condition on line 57 was never false if stack is None: 

stack = traceback.format_stack() 

typ = 'error' 

else: 

typ = 'error (from exception)' 

_log_stack(typ, stack) 

log.message.error(message) 

global_bridge.show(usertypes.MessageLevel.error, message, replace) 

 

 

def warning(message, *, replace=False): 

"""Convenience function to display a warning message in the statusbar. 

 

Args: 

message: The message to show 

replace: Replace existing messages with replace=True 

""" 

_log_stack('warning', traceback.format_stack()) 

log.message.warning(message) 

global_bridge.show(usertypes.MessageLevel.warning, message, replace) 

 

 

def info(message, *, replace=False): 

"""Convenience function to display an info message in the statusbar. 

 

Args: 

message: The message to show 

replace: Replace existing messages with replace=True 

""" 

log.message.info(message) 

global_bridge.show(usertypes.MessageLevel.info, message, replace) 

 

 

def _build_question(title, text=None, *, mode, default=None, abort_on=(), 

url=None, bc): 

"""Common function for ask/ask_async.""" 

if not isinstance(mode, usertypes.PromptMode): 

bc[11][0] = True 

raise TypeError("Mode {} is no PromptMode member!".format(mode)) 

question = usertypes.Question() 

question.title = title 

question.text = text 

question.mode = mode 

question.default = default 

question.url = url 

for sig in abort_on: 

bc[11][1] = True 

sig.connect(question.abort) 

return question 

 

 

def ask(*args, **kwargs): 

"""Ask a modular question in the statusbar (blocking). 

 

Args: 

message: The message to display to the user. 

mode: A PromptMode. 

default: The default value to display. 

text: Additional text to show 

abort_on: A list of signals which abort the question if emitted. 

 

Return: 

The answer the user gave or None if the prompt was cancelled. 

""" 

question = _build_question(*args, **kwargs) # pylint: disable=missing-kwoa 

global_bridge.ask(question, blocking=True) 

answer = question.answer 

question.deleteLater() 

return answer 

 

 

def ask_async(title, mode, handler, **kwargs): 

"""Ask an async question in the statusbar. 

 

Args: 

message: The message to display to the user. 

mode: A PromptMode. 

handler: The function to get called with the answer as argument. 

default: The default value to display. 

text: Additional text to show. 

""" 

question = _build_question(title, mode=mode, **kwargs) 

question.answered.connect(handler) 

question.completed.connect(question.deleteLater) 

global_bridge.ask(question, blocking=False) 

 

 

def confirm_async(*, yes_action, no_action=None, cancel_action=None, 

**kwargs): 

"""Ask a yes/no question to the user and execute the given actions. 

 

Args: 

message: The message to display to the user. 

yes_action: Callable to be called when the user answered yes. 

no_action: Callable to be called when the user answered no. 

cancel_action: Callable to be called when the user cancelled the 

question. 

default: True/False to set a default value, or None. 

text: Additional text to show. 

 

Return: 

The question object. 

""" 

kwargs['mode'] = usertypes.PromptMode.yesno 

question = _build_question(**kwargs) # pylint: disable=missing-kwoa 

question.answered_yes.connect(yes_action) 

if no_action is not None: 

question.answered_no.connect(no_action) 

if cancel_action is not None: 

question.cancelled.connect(cancel_action) 

 

question.completed.connect(question.deleteLater) 

global_bridge.ask(question, blocking=False) 

return question 

 

 

class GlobalMessageBridge(QObject): 

 

"""Global (not per-window) message bridge for errors/infos/warnings. 

 

Attributes: 

_connected: Whether a slot is connected and we can show messages. 

_cache: Messages shown while we were not connected. 

 

Signals: 

show_message: Show a message 

arg 0: A MessageLevel member 

arg 1: The text to show 

arg 2: Whether to replace other messages with 

replace=True. 

prompt_done: Emitted when a prompt was answered somewhere. 

ask_question: Ask a question to the user. 

arg 0: The Question object to ask. 

arg 1: Whether to block (True) or ask async (False). 

 

IMPORTANT: Slots need to be connected to this signal via 

a Qt.DirectConnection! 

mode_left: Emitted when a keymode was left in any window. 

""" 

 

show_message = pyqtSignal(usertypes.MessageLevel, str, bool) 

prompt_done = pyqtSignal(usertypes.KeyMode) 

ask_question = pyqtSignal(usertypes.Question, bool) 

mode_left = pyqtSignal(usertypes.KeyMode) 

clear_messages = pyqtSignal() 

 

def __init__(self, parent=None): 

super().__init__(parent) 

self._connected = False 

self._cache = [] 

 

def ask(self, question, blocking, *, log_stack=False): 

"""Ask a question to the user. 

 

Note this method doesn't return the answer, it only blocks. The caller 

needs to construct a Question object and get the answer. 

 

Args: 

question: A Question object. 

blocking: Whether to return immediately or wait until the 

question is answered. 

log_stack: ignored 

""" 

self.ask_question.emit(question, blocking) 

 

def show(self, level, text, replace=False): 

"""Show the given message.""" 

if self._connected: 

self.show_message.emit(level, text, replace) 

else: 

self._cache.append((level, text, replace)) 

 

def flush(self): 

"""Flush messages which accumulated while no handler was connected. 

 

This is so we don't miss messages shown during some early init phase. 

It needs to be called once the show_message signal is connected. 

""" 

self._connected = True 

for args in self._cache: 

self.show(*args) 

self._cache = [] 

 

 

class MessageBridge(QObject): 

 

"""Bridge for messages to be shown in the statusbar. 

 

Signals: 

s_set_text: Set a persistent text in the statusbar. 

arg: The text to set. 

s_maybe_reset_text: Reset the text if it hasn't been changed yet. 

arg: The expected text. 

""" 

 

s_set_text = pyqtSignal(str) 

s_maybe_reset_text = pyqtSignal(str) 

 

def __repr__(self): 

return utils.get_repr(self) 

 

def set_text(self, text, *, log_stack=False): 

"""Set the normal text of the statusbar. 

 

Args: 

text: The text to set. 

log_stack: ignored 

""" 

text = str(text) 

log.message.debug(text) 

self.s_set_text.emit(text) 

 

def maybe_reset_text(self, text, *, log_stack=False): 

"""Reset the text in the statusbar if it matches an expected text. 

 

Args: 

text: The expected text. 

log_stack: ignored 

""" 

self.s_maybe_reset_text.emit(str(text)) 

 

 

global_bridge = GlobalMessageBridge()