1
# Copyright (C) 2005 Dan Loda <danloda@gmail.com>
 
 
3
# This program is free software; you can redistribute it and/or modify
 
 
4
# it under the terms of the GNU General Public License as published by
 
 
5
# the Free Software Foundation; either version 2 of the License, or
 
 
6
# (at your option) any later version.
 
 
8
# This program is distributed in the hope that it will be useful,
 
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
# GNU General Public License for more details.
 
 
13
# You should have received a copy of the GNU General Public License
 
 
14
# along with this program; if not, write to the Free Software
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
25
from bzrlib.errors import NoSuchRevision
 
 
27
from colormap import AnnotateColorMap, AnnotateColorSaturation
 
 
28
from logview import LogView
 
 
29
from spanselector import SpanSelector
 
 
42
class GAnnotateWindow(gtk.Window):
 
 
43
    """Annotate window."""
 
 
45
    def __init__(self, all=False, plain=False):
 
 
49
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
 
 
51
        self.set_icon(self.render_icon(gtk.STOCK_FIND, gtk.ICON_SIZE_BUTTON))
 
 
52
        self.annotate_colormap = AnnotateColorSaturation()
 
 
57
            self.span_selector.hide()
 
 
59
    def annotate(self, branch, file_id):
 
 
62
        # [revision id, line number, committer, revno, highlight color, line]
 
 
63
        self.annomodel = gtk.ListStore(gobject.TYPE_STRING,
 
 
73
            branch.repository.lock_read()
 
 
74
            for line_no, (revision, revno, line)\
 
 
75
                    in enumerate(self._annotate(branch, file_id)):
 
 
76
                if revision.revision_id == last_seen and not self.all:
 
 
77
                    revno = committer = ""
 
 
79
                    last_seen = revision.revision_id
 
 
80
                    committer = revision.committer
 
 
82
                if revision.revision_id not in self.revisions:
 
 
83
                    self.revisions[revision.revision_id] = revision
 
 
85
                self.annomodel.append([revision.revision_id,
 
 
94
                self._set_oldest_newest()
 
 
95
                # Recall that calling activate_default will emit "span-changed",
 
 
96
                # so self._span_changed_cb will take care of initial highlighting
 
 
97
                self.span_selector.activate_default()
 
 
99
            branch.repository.unlock()
 
 
102
        self.annoview.set_model(self.annomodel)
 
 
103
        self.annoview.grab_focus()
 
 
105
    def jump_to_line(self, lineno):
 
 
106
        if lineno > len(self.annomodel) or lineno < 1:
 
 
108
            # FIXME:should really deal with this in the gui. Perhaps a status
 
 
110
            print("gannotate: Line number %d does't exist. Defaulting to "
 
 
115
        self.annoview.set_cursor(row)
 
 
117
    def _annotate(self, branch, file_id):
 
 
118
        rev_hist = branch.revision_history()
 
 
119
        repository = branch.repository
 
 
120
        rev_tree = repository.revision_tree(branch.last_revision())
 
 
121
        rev_id = rev_tree.inventory[file_id].revision
 
 
122
        weave = repository.weave_store.get_weave(file_id,
 
 
123
                                                 branch.get_transaction())
 
 
125
        revision_cache = RevisionCache(repository)
 
 
126
        for origin, text in weave.annotate_iter(rev_id):
 
 
129
                revision = revision_cache.get_revision(rev_id)
 
 
130
                if rev_id in rev_hist:
 
 
131
                    revno = branch.revision_id_to_revno(rev_id)
 
 
134
            except NoSuchRevision:
 
 
135
                revision = NoneRevision(rev_id)
 
 
138
            yield revision, revno, text
 
 
140
    def _set_oldest_newest(self):
 
 
141
        rev_dates = map(lambda i: self.revisions[i].timestamp, self.revisions)
 
 
142
        oldest = min(rev_dates)
 
 
143
        newest = max(rev_dates)
 
 
145
        span = self._span_from_seconds(time.time() - oldest)
 
 
146
        self.span_selector.set_to_oldest_span(span)
 
 
148
        span = self._span_from_seconds(newest - oldest)
 
 
149
        self.span_selector.set_newest_to_oldest_span(span)
 
 
151
    def _span_from_seconds(self, seconds):
 
 
152
        return (seconds / (24 * 60 * 60))
 
 
154
    def _span_changed_cb(self, w, span):
 
 
155
        self.annotate_colormap.set_span(span)
 
 
157
        self.annomodel.foreach(self._highlight_annotation, now)
 
 
159
    def _highlight_annotation(self, model, path, iter, now):
 
 
160
        revision_id, = model.get(iter, REVISION_ID_COL)
 
 
161
        revision = self.revisions[revision_id]
 
 
162
        model.set(iter, HIGHLIGHT_COLOR_COL,
 
 
163
                  self.annotate_colormap.get_color(revision, now))
 
 
165
    def _show_log(self, w):
 
 
166
        (path, col) = self.annoview.get_cursor()
 
 
167
        rev_id = self.annomodel[path][REVISION_ID_COL]
 
 
168
        self.logview.set_revision(self.revisions[rev_id])
 
 
171
        self.logview = self._create_log_view()
 
 
172
        self.annoview = self._create_annotate_view()
 
 
173
        self.span_selector = self._create_span_selector()
 
 
175
        vbox = gtk.VBox(False, 12)
 
 
176
        vbox.set_border_width(12)
 
 
179
        sw = gtk.ScrolledWindow()
 
 
180
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
 
181
        sw.set_shadow_type(gtk.SHADOW_IN)
 
 
182
        sw.add(self.annoview)
 
 
185
        self.pane = pane = gtk.VPaned()
 
 
187
        pane.add2(self.logview)
 
 
189
        vbox.pack_start(pane, expand=True, fill=True)
 
 
191
        hbox = gtk.HBox(True, 6)
 
 
192
        hbox.pack_start(self.span_selector, expand=False, fill=True)
 
 
193
        hbox.pack_start(self._create_button_box(), expand=False, fill=True)
 
 
195
        vbox.pack_start(hbox, expand=False, fill=True)
 
 
199
    def _create_annotate_view(self):
 
 
201
        tv.set_rules_hint(False)
 
 
202
        tv.connect("cursor-changed", self._show_log)
 
 
205
        cell = gtk.CellRendererText()
 
 
206
        cell.set_property("xalign", 1.0)
 
 
207
        cell.set_property("ypad", 0)
 
 
208
        cell.set_property("family", "Monospace")
 
 
209
        cell.set_property("cell-background-gdk",
 
 
210
                          tv.get_style().bg[gtk.STATE_NORMAL])
 
 
211
        col = gtk.TreeViewColumn()
 
 
212
        col.set_resizable(False)
 
 
213
        col.pack_start(cell, expand=True)
 
 
214
        col.add_attribute(cell, "text", LINE_NUM_COL)
 
 
215
        tv.append_column(col)
 
 
217
        cell = gtk.CellRendererText()
 
 
218
        cell.set_property("ypad", 0)
 
 
219
        cell.set_property("ellipsize", pango.ELLIPSIZE_END)
 
 
220
        cell.set_property("cell-background-gdk",
 
 
221
                          self.get_style().bg[gtk.STATE_NORMAL])
 
 
222
        col = gtk.TreeViewColumn("Committer")
 
 
223
        col.set_resizable(True)
 
 
224
        col.pack_start(cell, expand=True)
 
 
225
        col.add_attribute(cell, "text", COMMITTER_COL)
 
 
226
        tv.append_column(col)
 
 
228
        cell = gtk.CellRendererText()
 
 
229
        cell.set_property("xalign", 1.0)
 
 
230
        cell.set_property("ypad", 0)
 
 
231
        cell.set_property("cell-background-gdk",
 
 
232
                          self.get_style().bg[gtk.STATE_NORMAL])
 
 
233
        col = gtk.TreeViewColumn("Revno")
 
 
234
        col.set_resizable(False)
 
 
235
        col.pack_start(cell, expand=True)
 
 
236
        col.add_attribute(cell, "markup", REVNO_COL)
 
 
237
        tv.append_column(col)
 
 
239
        cell = gtk.CellRendererText()
 
 
240
        cell.set_property("ypad", 0)
 
 
241
        cell.set_property("family", "Monospace")
 
 
242
        col = gtk.TreeViewColumn()
 
 
243
        col.set_resizable(False)
 
 
244
        col.pack_start(cell, expand=True)
 
 
245
#        col.add_attribute(cell, "foreground", HIGHLIGHT_COLOR_COL)
 
 
246
        col.add_attribute(cell, "background", HIGHLIGHT_COLOR_COL)
 
 
247
        col.add_attribute(cell, "text", TEXT_LINE_COL)
 
 
248
        tv.append_column(col)
 
 
250
        tv.set_search_column(LINE_NUM_COL)
 
 
254
    def _create_span_selector(self):
 
 
256
        ss.connect("span-changed", self._span_changed_cb)
 
 
261
    def _create_log_view(self):
 
 
267
    def _create_button_box(self):
 
 
268
        box = gtk.HButtonBox()
 
 
269
        box.set_layout(gtk.BUTTONBOX_END)
 
 
272
        button = gtk.Button()
 
 
273
        button.set_use_stock(True)
 
 
274
        button.set_label("gtk-close")
 
 
275
        button.connect("clicked", lambda w: self.destroy())
 
 
278
        box.pack_start(button, expand=False, fill=False)
 
 
286
    For when a revision is referenced but not present.
 
 
289
    def __init__(self, revision_id):
 
 
290
        self.revision_id = revision_id
 
 
298
class RevisionCache(object):
 
 
299
    """A caching revision source"""
 
 
300
    def __init__(self, real_source):
 
 
301
        self.__real_source = real_source
 
 
304
    def get_revision(self, revision_id):
 
 
305
        if revision_id not in self.__cache:
 
 
306
            revision = self.__real_source.get_revision(revision_id)
 
 
307
            self.__cache[revision_id] = revision
 
 
308
        return self.__cache[revision_id]