~kvilhaugsvik/qbzr/qshelve_all

187 by Lukáš Lalinský
qcat: New command.
1
# -*- coding: utf-8 -*-
2
#
3
# QBzr - Qt frontend to Bazaar commands
4
# Copyright (C) 2007 Lukáš Lalinský <lalinsky@gmail.com>
5
#
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
20
import os
187 by Lukáš Lalinský
qcat: New command.
21
from PyQt4 import QtCore, QtGui
240 by Alexander Belchenko
move most of python modules to subpackage 'lib'
22
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
23
from bzrlib import errors, osutils
24
from bzrlib.branch import Branch
25
240 by Alexander Belchenko
move most of python modules to subpackage 'lib'
26
from bzrlib.plugins.qbzr.lib.i18n import gettext
27
from bzrlib.plugins.qbzr.lib.util import (
187 by Lukáš Lalinský
qcat: New command.
28
    BTN_CLOSE,
29
    QBzrWindow,
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
30
    ThrobberWidget,
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
31
    file_extension,
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
32
    get_set_encoding,
628 by Alexander Belchenko
cat.py: factor out method to create actual file content viewer; restored adjusting window title to the type of content.
33
    runs_in_loading_queue,
1333 by Gary van der Merwe
Make util function to get monospace font. Try use "Monospace" before using "Courier New".
34
    get_monospace_font,
187 by Lukáš Lalinský
qcat: New command.
35
    )
580 by Gary van der Merwe
Keep track of the current window for the ui_factory.
36
from bzrlib.plugins.qbzr.lib.uifactory import ui_current_widget
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
37
from bzrlib.plugins.qbzr.lib.trace import reports_exception
985.1.3 by Naoki INADA
Add encoding selector to qcat.
38
from bzrlib.plugins.qbzr.lib.encoding_selector import EncodingSelector
1113.2.2 by Gary van der Merwe
qcat: Use new syntaxhighlighter.
39
from bzrlib.plugins.qbzr.lib.syntaxhighlighter import highlight_document
1113.2.3 by Gary van der Merwe
qcat: Show line numbers.
40
from bzrlib.plugins.qbzr.lib.texteditannotate import LineNumberEditerFrame
187 by Lukáš Lalinský
qcat: New command.
41
240 by Alexander Belchenko
move most of python modules to subpackage 'lib'
42
259 by Lukáš Lalinský
qcat: Show hex dump for binary files
43
def hexdump(data):
44
    content = []
45
    for i in range(0, len(data), 16):
46
        hexdata = []
47
        chardata = []
48
        for c in data[i:i+16]:
49
            j = ord(c)
50
            hexdata.append('%02x' % j)
51
            if j >= 32 and j < 128:
52
                chardata.append(c)
53
            else:
54
                chardata.append('.')
55
        for c in range(16 - len(hexdata)):
56
            hexdata.append('  ')
57
            chardata.append(' ')
58
        line = '%08x  ' % i + ' '.join(hexdata[:8]) + '  ' + ' '.join(hexdata[8:]) + '  |' + ''.join(chardata) + '|'
59
        content.append(line)
60
    return '\n'.join(content)
61
62
187 by Lukáš Lalinský
qcat: New command.
63
class QBzrCatWindow(QBzrWindow):
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
64
    """Show content of versioned file/symlink."""
187 by Lukáš Lalinský
qcat: New command.
65
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
66
    def __init__(self, filename=None, revision=None,
67
                 tree=None, file_id=None, encoding=None,
68
                 parent=None):
69
        """Create qcat window."""
70
        
71
        self.filename = filename
72
        self.revision = revision
73
        self.tree = tree
1136 by Alexander Belchenko
refactoring of qcat internals.
74
        if tree:
1151 by Alexander Belchenko
fixed problem with invoking qcat on file from RevisionTree (there is no real branch in it).
75
            self.branch = getattr(tree, 'branch', None)
76
            if self.branch is None:
77
                self.branch = FakeBranch()
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
78
        self.file_id = file_id
79
        self.encoding = encoding
1100 by Alexander Belchenko
encoding_selector.py: correctly handle initial_encoding.
80
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
81
        if (not self.filename) and self.tree and self.file_id:
82
            self.filename = self.tree.id2path(self.file_id)
1100 by Alexander Belchenko
encoding_selector.py: correctly handle initial_encoding.
83
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
84
        QBzrWindow.__init__(self, [gettext("View"), self.filename], parent)
187 by Lukáš Lalinský
qcat: New command.
85
        self.restoreSize("cat", (780, 580))
86
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
87
        self.throbber = ThrobberWidget(self)
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
88
        self.buttonbox = self.create_button_box(BTN_CLOSE)
1138 by Alexander Belchenko
cat.py: added helper function to create encoding_selector
89
        self.encoding_selector = self._create_encoding_selector()
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
90
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
91
        self.vbox = QtGui.QVBoxLayout(self.centralwidget)
92
        self.vbox.addWidget(self.throbber)
93
        self.vbox.addStretch()
985.1.3 by Naoki INADA
Add encoding selector to qcat.
94
985.2.1 by Alexander Belchenko
fixed layout warnings
95
        hbox = QtGui.QHBoxLayout()
985.1.3 by Naoki INADA
Add encoding selector to qcat.
96
        hbox.addWidget(self.encoding_selector)
97
        hbox.addWidget(self.buttonbox)
98
        self.vbox.addLayout(hbox)
99
1138 by Alexander Belchenko
cat.py: added helper function to create encoding_selector
100
    def _create_encoding_selector(self):
101
        encoding_selector = EncodingSelector(self.encoding,
102
            gettext("Encoding:"),
103
            self._on_encoding_changed)
104
        # disable encoding selector,
105
        # it will be enabled later only for text files
106
        encoding_selector.setDisabled(True)
107
        return encoding_selector
108
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
109
    def show(self):
110
        # we show the bare form as soon as possible.
111
        QBzrWindow.show(self)
1133.1.1 by Gary van der Merwe
qcat: Add a test for bug 489244.
112
        QtCore.QTimer.singleShot(0, self.load)
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
113
    
613.1.1 by Gary van der Merwe
Implement loading queue. Re-enable user events during report_transport_activity.
114
    @runs_in_loading_queue
580 by Gary van der Merwe
Keep track of the current window for the ui_factory.
115
    @ui_current_widget
624.1.10 by Gary van der Merwe
Fix @reports_exception declarations.
116
    @reports_exception()
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
117
    def load(self):
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
118
        self.throbber.show()
119
        self.processEvents()
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
120
        try:
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
121
            if not self.tree:
122
                branch, relpath = Branch.open_containing(self.filename)
1136 by Alexander Belchenko
refactoring of qcat internals.
123
                self.branch = branch
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
124
                self.encoding = get_set_encoding(self.encoding, branch)
985.1.13 by Naoki INADA
Fix qcat ignores configured encoding.
125
                self.encoding_selector.encoding = self.encoding
126
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
127
                if self.revision is None:
128
                    self.tree = branch.basis_tree()
129
                else:
130
                    revision_id = self.revision[0].in_branch(branch).rev_id
131
                    self.tree = branch.repository.revision_tree(revision_id)
132
                
133
                self.file_id = self.tree.path2id(relpath)
134
            
135
            if not self.file_id:
136
                self.file_id = self.tree.path2id(self.filename)
137
                
138
            if not self.file_id:
139
                raise errors.BzrCommandError(
140
                    "%r is not present in revision %s" % (
141
                        self.filename, self.tree.get_revision_id()))
142
            
143
            self.tree.lock_read()
144
            try:
145
                kind = self.tree.kind(self.file_id)
146
                if kind == 'file':
147
                    text = self.tree.get_file_text(self.file_id)
148
                elif kind == 'symlink':
149
                    text = self.tree.get_symlink_target(self.file_id)
150
                else:
151
                    text = ''
152
            finally:
153
                self.tree.unlock()
545.1.29 by Gary van der Merwe
qcat: Add Throbber.
154
            self.processEvents()
628 by Alexander Belchenko
cat.py: factor out method to create actual file content viewer; restored adjusting window title to the type of content.
155
985.1.3 by Naoki INADA
Add encoding selector to qcat.
156
            self.text = text
157
            self.kind = kind
628 by Alexander Belchenko
cat.py: factor out method to create actual file content viewer; restored adjusting window title to the type of content.
158
            self._create_and_show_browser(self.filename, text, kind)
624.1.7 by Gary van der Merwe
Use @reports_exception everywhere.
159
        finally:
160
            self.throbber.hide()
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
161
628 by Alexander Belchenko
cat.py: factor out method to create actual file content viewer; restored adjusting window title to the type of content.
162
    def _create_and_show_browser(self, filename, text, kind):
163
        """Create browser object for given file and then attach it to GUI.
164
165
        @param  filename:   filename used for differentiate between images
166
                            and simply binary files.
167
        @param  text:       raw file content.
168
        @param  kind:       filesystem kind: file, symlink, directory
169
        """
170
        type_, fview = self.detect_content_type(filename, text, kind)
171
        # update title
649 by Alexander Belchenko
avoid unnecessary translations.
172
        title = "View " + type_
173
        self.set_title([gettext(title), filename])
628 by Alexander Belchenko
cat.py: factor out method to create actual file content viewer; restored adjusting window title to the type of content.
174
        # create and show browser
175
        self.browser = fview(filename, text)
176
        self.vbox.insertWidget(1, self.browser, 1)
177
        # set focus on content
178
        self.browser.setFocus()
179
265 by Alexander Belchenko
qcat: now able to show symlink target. Window title better reflects content type.
180
    def detect_content_type(self, relpath, text, kind='file'):
181
        """Return (file_type, viewer_factory) based on kind, text and relpath.
182
        Supported file types: text, image, binary
183
        """
184
        if kind == 'file':
185
            if not '\0' in text:
186
                return 'text file', self._create_text_view
187
            else:
188
                ext = file_extension(relpath).lower()
189
                image_exts = ['.'+str(i)
190
                    for i in QtGui.QImageReader.supportedImageFormats()]
191
                if ext in image_exts:
192
                    return 'image file', self._create_image_view
193
                else:
194
                    return 'binary file', self._create_hexdump_view
195
        else:
196
            return kind, self._create_symlink_view
1136 by Alexander Belchenko
refactoring of qcat internals.
197
198
    def _set_text(self, edit_widget, relpath, text, encoding=None):
199
        """Set plain text to widget, as unicode.
200
201
        @param edit_widget: edit widget to view the text.
202
        @param relpath: filename (required for syntax highlighting to detect
203
            file type).
204
        @param text: plain non-unicode text (bytes).
205
        @param encoding: text encoding (default: utf-8).
206
        """
207
        text = text.decode(encoding or 'utf-8', 'replace')
208
        edit_widget.setPlainText(text)
209
        highlight_document(edit_widget, relpath)
985.1.3 by Naoki INADA
Add encoding selector to qcat.
210
211
    def _create_text_view(self, relpath, text):
1136 by Alexander Belchenko
refactoring of qcat internals.
212
        """Create widget to show text files.
213
        @return: created widget with loaded text.
214
        """
215
        browser = LineNumberEditerFrame(self)
216
        edit = browser.edit
217
        edit.setReadOnly(True)
1333 by Gary van der Merwe
Make util function to get monospace font. Try use "Monospace" before using "Courier New".
218
        edit.document().setDefaultFont(get_monospace_font())
1136 by Alexander Belchenko
refactoring of qcat internals.
219
        self._set_text(edit, relpath, text, self.encoding)
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
220
        self.encoding_selector.setEnabled(True)
1136 by Alexander Belchenko
refactoring of qcat internals.
221
        return browser
187 by Lukáš Lalinský
qcat: New command.
222
985.1.13 by Naoki INADA
Fix qcat ignores configured encoding.
223
    def _on_encoding_changed(self, encoding):
1136 by Alexander Belchenko
refactoring of qcat internals.
224
        """Event handler for EncodingSelector.
225
        It sets file text to browser again with new encoding.
226
        """
985.1.3 by Naoki INADA
Add encoding selector to qcat.
227
        self.encoding = encoding
1136 by Alexander Belchenko
refactoring of qcat internals.
228
        branch = self.branch
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
229
        if branch is None:
1136 by Alexander Belchenko
refactoring of qcat internals.
230
            branch = Branch.open_containing(self.filename)[0]
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
231
        if branch:
232
            get_set_encoding(encoding, branch)
1136 by Alexander Belchenko
refactoring of qcat internals.
233
        self._set_text(self.browser.edit, self.filename, self.text, self.encoding)
234
235
    def _create_simple_text_browser(self):
236
        """Create and return simple widget to show text-like content."""
237
        browser = QtGui.QPlainTextEdit(self)
238
        browser.setReadOnly(True)
1333 by Gary van der Merwe
Make util function to get monospace font. Try use "Monospace" before using "Courier New".
239
        browser.document().setDefaultFont(get_monospace_font())
1136 by Alexander Belchenko
refactoring of qcat internals.
240
        return browser
985.1.3 by Naoki INADA
Add encoding selector to qcat.
241
265 by Alexander Belchenko
qcat: now able to show symlink target. Window title better reflects content type.
242
    def _create_symlink_view(self, relpath, target):
1136 by Alexander Belchenko
refactoring of qcat internals.
243
        """Create widget to show symlink target.
244
        @return: created widget with loaded content.
245
        """
246
        browser = self._create_simple_text_browser()
247
        browser.setPlainText('-> ' + target.decode('utf-8', 'replace'))
248
        return browser
259 by Lukáš Lalinský
qcat: Show hex dump for binary files
249
250
    def _create_hexdump_view(self, relpath, data):
1136 by Alexander Belchenko
refactoring of qcat internals.
251
        """Create widget to show content of binary files.
252
        @return: created widget with loaded content.
253
        """
254
        browser = self._create_simple_text_browser()
255
        browser.setPlainText(hexdump(data))
256
        return browser
187 by Lukáš Lalinský
qcat: New command.
257
265 by Alexander Belchenko
qcat: now able to show symlink target. Window title better reflects content type.
258
    def _create_image_view(self, relpath, data):
1136 by Alexander Belchenko
refactoring of qcat internals.
259
        """Create widget to show image file.
260
        @return: created widget with loaded image.
261
        """
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
262
        self.pixmap = QtGui.QPixmap()
265 by Alexander Belchenko
qcat: now able to show symlink target. Window title better reflects content type.
263
        self.pixmap.loadFromData(data)
254 by Alexander Belchenko
qcat: show content of image file as image (#242070)
264
        self.item = QtGui.QGraphicsPixmapItem(self.pixmap)
265
        self.scene = QtGui.QGraphicsScene(self.item.boundingRect())
266
        self.scene.addItem(self.item)
1136 by Alexander Belchenko
refactoring of qcat internals.
267
        return QtGui.QGraphicsView(self.scene)
187 by Lukáš Lalinský
qcat: New command.
268
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
269
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
270
class QBzrViewWindow(QBzrCatWindow):
271
    """Show content of file/symlink from the disk."""
272
273
    def __init__(self, filename=None, encoding=None, parent=None):
274
        """Construct GUI.
275
276
        @param  filename:   filesystem object to view.
277
        @param  encoding:   use this encoding to decode text file content
278
                            to unicode.
279
        @param  parent:     parent widget.
280
        """
281
        QBzrWindow.__init__(self, [gettext("View"), filename], parent)
282
        self.restoreSize("cat", (780, 580))
283
284
        self.filename = filename
285
        self.encoding = encoding
286
287
        self.buttonbox = self.create_button_box(BTN_CLOSE)
1138 by Alexander Belchenko
cat.py: added helper function to create encoding_selector
288
        self.encoding_selector = self._create_encoding_selector()
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
289
        self.branch = FakeBranch()
290
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
291
        self.vbox = QtGui.QVBoxLayout(self.centralwidget)
292
        self.vbox.addStretch()
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
293
        hbox = QtGui.QHBoxLayout()
294
        hbox.addWidget(self.encoding_selector)
295
        hbox.addWidget(self.buttonbox)
296
        self.vbox.addLayout(hbox)
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
297
298
    def load(self):
299
        kind = osutils.file_kind(self.filename)
300
        text = ''
301
        if kind == 'file':
302
            f = open(self.filename, 'rb')
303
            try:
304
                text = f.read()
305
            finally:
306
                f.close()
307
        elif kind == 'symlink':
308
            text = os.readlink(self.filename)
1137 by Alexander Belchenko
added encoding_selector for qviewer command; disabled encoding_selector for images, binary files and symlinks views.
309
        self.text = text
629 by Alexander Belchenko
qviewer: new command to view content of file from disk.
310
        self._create_and_show_browser(self.filename, text, kind)
311
312
1151 by Alexander Belchenko
fixed problem with invoking qcat on file from RevisionTree (there is no real branch in it).
313
class FakeBranch(object):
314
    """Special branch object to disable save encodings to branch.conf"""
315
316
    def __init__(self):
317
        pass
318
319
    def __nonzero__(self):
320
        return False
321
322
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
323
def cat_to_native_app(tree, relpath):
324
    """Extract file content to temp directory and then launch
325
    native application to open it.
467.1.3 by Alexander Belchenko
cat_to_native_app(): try to cleanup $TMP/QBzr after launching native application (unlink files older than 1 minute)
326
327
    @param  tree:   RevisionTree object.
328
    @param  relpath:    path to file relative to tree root.
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
329
    @raise  KindError:  if relpath entry has not file kind.
330
    @return:    True if native application was launched.
331
    """
332
    file_id = tree.path2id(relpath)
333
    kind = tree.kind(file_id)
334
    if kind != 'file':
335
        raise KindError('cat to native application is not supported '
336
            'for entry of kind %r' % kind)
337
    # make temp file
338
    import os
339
    import tempfile
514.1.2 by Alexander Belchenko
Prepare 0.9.5 release
340
    qdir = os.path.join(tempfile.gettempdir(), 'QBzr', 'qcat')
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
341
    if not os.path.isdir(qdir):
514.1.2 by Alexander Belchenko
Prepare 0.9.5 release
342
        os.makedirs(qdir)
467.1.3 by Alexander Belchenko
cat_to_native_app(): try to cleanup $TMP/QBzr after launching native application (unlink files older than 1 minute)
343
    basename = os.path.basename(relpath)
344
    fname = os.path.join(qdir, basename)
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
345
    f = open(fname, 'wb')
346
    tree.lock_read()
347
    try:
348
        f.write(tree.get_file_text(file_id))
349
    finally:
350
        tree.unlock()
351
        f.close()
352
    # open it
353
    url = QtCore.QUrl(fname)
354
    result = QtGui.QDesktopServices.openUrl(url)
467.1.3 by Alexander Belchenko
cat_to_native_app(): try to cleanup $TMP/QBzr after launching native application (unlink files older than 1 minute)
355
    # now application is about to start and user will work with file
356
    # so we can do cleanup in "background"
357
    import time
358
    limit = time.time() - 60    # files older than 1 minute
359
    files = os.listdir(qdir)
360
    for i in files[:20]:
361
        if i == basename:
362
            continue
363
        fname = os.path.join(qdir, i)
364
        st = os.lstat(fname)
365
        if st.st_mtime > limit:
366
            continue
367
        try:
368
            os.unlink(fname)
369
        except (OSError, IOError):
370
            pass
371
    #
467.1.2 by Alexander Belchenko
qcat --native: move actual code to cat.py: cat_to_native_app() function
372
    return result
373
374
375
class KindError(errors.BzrError):
376
    pass