Staging
v0.8.1
v0.8.1
https://foss.heptapod.net/mercurial/hgview
Tip revision: df7af11a5ea2143444341e527210ba220bc45f40 authored by Carine Dengler on 11 February 2020, 15:15:42 UTC
[pkg] version 1.13.1
[pkg] version 1.13.1
Tip revision: df7af11
quickbar.py
# -*- coding: utf-8 -*-
# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program 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 2 of the License, or (at your option) any later
# version.
#
# This program 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
# this program. If not, see <http://www.gnu.org/licenses/>.
"""
Qt4 QToolBar-based class for quick bars XXX
"""
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal
from mercurial import pycompat
from hgviewlib.util import tounicode, binary
from hgviewlib.qt4.mixins import ActionsMixin
Qt = QtCore.Qt
class QuickBar(QtGui.QToolBar, ActionsMixin):
esc_shortcut_disabled = pyqtSignal(bool)
unhidden = pyqtSignal()
def __init__(self, parent=None, name='Absctract'):
# used to remember who had the focus before bar steel it
self.name = name
self._focusw = None
QtGui.QToolBar.__init__(self, self.name, parent)
ActionsMixin.__init__(self)
self.setIconSize(QtCore.QSize(16,16))
self.setFloatable(False)
self.setMovable(False)
self.setAllowedAreas(Qt.BottomToolBarArea)
self.createActions()
self.createContent()
if parent:
parent = parent.window()
if isinstance(parent, QtGui.QMainWindow):
parent.addToolBar(Qt.BottomToolBarArea, self)
self.setVisible(False)
def createActions(self):
self.add_action(
'close', self.tr("Close"),
icon='close',
tip=self.tr("Close toolbar"),
callback=self.hide,
)
def setVisible(self, visible=True):
if visible and not self.isVisible():
self.unhidden.emit()
self._focusw = QtGui.QApplication.focusWidget()
QtGui.QToolBar.setVisible(self, visible)
self.esc_shortcut_disabled[bool].emit(not visible)
if not visible and self._focusw:
self._focusw.setFocus()
self._focusw = None
def createContent(self):
raise NotImplementedError
def hide(self):
self.setVisible(False)
def unhide(self):
self.setVisible(True)
def cancel(self):
self.hide()
class FindQuickBar(QuickBar):
to_find = pyqtSignal(str)
to_find_next = pyqtSignal(str)
canceled = pyqtSignal()
message_logged = pyqtSignal(str, int)
def __init__(self, parent, name='find'):
QuickBar.__init__(self, parent, name)
self.currenttext = ''
def createActions(self):
QuickBar.createActions(self)
self.add_action(
'findnext', self.tr("Find next"),
icon='forward',
keys=QtGui.QKeySequence("Ctrl+N"),
tip=self.tr("Search for the next occurence"),
callback=self.find,
)
self.add_action(
'cancel', self.tr('Cancel'),
tip=self.tr("Cancel processing search"),
callback=self.cancel
)
def find(self, *args):
'''Scan the repository metadata and search for occurrences of the
text in the entry.
:note: do not scan if no text was provided'''
text = self.entry.text()
if not text: # do not strip() as user may want to find space sequences
self.message_logged.emit('Nothing to look for.', 1000)
return
if text == self.currenttext:
self.to_find_next.emit(text)
else:
self.currenttext = text
self.to_find.emit(text)
def cancel(self):
self.canceled.emit()
def setCancelEnabled(self, enabled=True):
self.set_action('cancel', enabled=enabled)
self.set_action('findnext', enabled=not enabled)
def createContent(self):
self.compl_model = QtGui.QStringListModel()
self.completer = QtGui.QCompleter(self.compl_model, self)
self.entry = QtGui.QLineEdit(self)
self.entry.setCompleter(self.completer)
self.addWidget(self.entry)
self.addActions(self.get_actions('findnext', 'cancel'))
self.setCancelEnabled(False)
self.entry.returnPressed.connect(self.find)
self.entry.textEdited[str].connect(self.find)
def setVisible(self, visible=True):
QuickBar.setVisible(self, visible)
if visible:
self.entry.setFocus()
self.entry.selectAll()
def text(self):
if self.isVisible() and self.currenttext.strip():
return self.currenttext
def __del__(self):
# prevent a warning in the console:
# QObject::startTimer: QTimer can only be used with threads started with QThread
self.entry.setCompleter(None)
class FindInGraphlogQuickBar(FindQuickBar):
revision_selected = pyqtSignal([], [int])
file_selected = pyqtSignal(str)
def __init__(self, parent, name='find'):
FindQuickBar.__init__(self, parent, name)
self._findinfile_iter = None
self._findinlog_iter = None
self._findindesc_iter = None
self._fileview = None
self._headerview = None
self._filter_files = None
self._mode = 'diff'
self.to_find.connect(self.on_find_text_changed)
self.to_find_next.connect(self.on_findnext)
self.canceled.connect(self.on_cancelsearch)
def setFilterFiles(self, files):
self._filter_files = files
def setModel(self, model):
self._model = model
def setMode(self, mode):
assert mode in ('diff', 'file'), mode
self._mode = mode
def attachFileView(self, fileview):
self._fileview = fileview
def attachHeaderView(self, view):
self._headerview = view
def find_in_graphlog(self, fromrev, fromfile=None):
"""
Find text in the whole repo from rev 'fromrev', from file
'fromfile' (if given) *excluded*
"""
text = self.entry.text()
graph = self._model.graph
idx = graph.index(fromrev)
for node in graph[idx:]:
rev = node.rev
ctx = self._model.repo[rev]
# XXX should be an re search with undecoded chars as '?'
if text in tounicode(ctx.description()):
yield rev, None
files = ctx.files()
if self._filter_files:
files = [x for x in files if x in self._filter_files]
if fromfile is not None and fromfile in files:
files = files[files.index(fromfile)+1:]
fromfile = None
for filename in files:
if self._mode == 'diff':
flag, data = self._model.graph.filedata(tounicode(filename), rev)
else:
data = ctx.filectx(filename).data()
if binary(data):
data = "binary file"
if data and text in data:
yield rev, tounicode(filename)
else:
yield None
def cancel(self):
if self.get_action('cancel').isEnabled():
self.canceled.emit()
else:
self.hide()
def on_cancelsearch(self, *args):
self._findinlog_iter = None
self.setCancelEnabled(False)
self.message_logged.emit('Search cancelled!', 2000)
def on_findnext(self):
"""
callback called by 'Find' quicktoolbar (on findnext signal)
"""
if self._findindesc_iter is not None:
for pos in self._findindesc_iter:
# just highlight next found text in fileview
# (handled by _findinfile_iter)
return
# no more found text in currently displayed file
self._findindesc_iter = None
if self._findinfile_iter is not None:
for pos in self._findinfile_iter:
# just highlight next found text in descview
# (handled by _findindesc_iter)
return
# no more found text in currently displayed file
self._findinfile_iter = None
if self._findinlog_iter is None:
# start searching in the graphlog from current position
rev = self._fileview.rev()
filename = self._fileview.filename()
self._findinlog_iter = self.find_in_graphlog(rev, filename)
self.setCancelEnabled(True)
self.find_next_in_log()
def find_next_in_log(self, step=0):
"""
to be called from 'on_find' callback (or recursively). Try to
find the next occurrence of searched text (as a 'background'
process, so the GUI is not frozen, and as a cancellable task).
"""
if self._findinlog_iter is None:
# when search has been cancelled
return
for next_find in self._findinlog_iter:
if next_find is None: # not yet found, let's animate a bit the GUI
if (step % 20) == 0:
self.message_logged.emit(
'Searching'+'.'*(step/20),
-1)
step += 1
QtCore.QTimer.singleShot(0, lambda: self.find_next_in_log(step % 80))
else:
self.message_logged.emit('', -1)
self.setCancelEnabled(False)
rev, filename = next_find
if rev is None:
self.revision_selected.emit()
else:
self.revision_selected[int].emit(rev)
text = self.entry.text()
if filename is None and self._headerview:
self._findindesc_iter = self._headerview.searchString(text)
self.on_findnext()
else:
self.file_selected.emit(tounicode(filename))
if self._fileview:
self._findinfile_iter = self._fileview.searchString(text)
self.on_findnext()
return
self.message_logged.emit(
'No more matches found in repository',
2000)
self.setCancelEnabled(False)
self._findinlog_iter = None
def on_find_text_changed(self, newtext):
"""
callback called by 'Find' quicktoolbar (on find signal)
"""
newtext = pycompat.unicode(newtext)
self._findinlog_iter = None
self._findinfile_iter = None
if self._headerview:
self._findindesc_iter = self._headerview.searchString(newtext)
if self._fileview:
self._findinfile_iter = self._fileview.searchString(newtext)
if newtext.strip():
if self._findindesc_iter is None and self._findindesc_iter is None:
self.message_logged.emit(
'Search string not found in current diff. '
'Hit "Find next" button to start searching '
'in the repository',
2000)
else:
self.on_findnext()