debug-viewer: fix names of actions/functions
[platform/upstream/gstreamer.git] / debug-viewer / GstDebugViewer / GUI / window.py
1 # -*- coding: utf-8; mode: python; -*-
2 #
3 #  GStreamer Debug Viewer - View and analyze GStreamer debug log files
4 #
5 #  Copyright (C) 2007 RenĂ© Stadler <mail@renestadler.de>
6 #
7 #  This program is free software; you can redistribute it and/or modify it
8 #  under the terms of the GNU General Public License as published by the Free
9 #  Software Foundation; either version 3 of the License, or (at your option)
10 #  any later version.
11 #
12 #  This program is distributed in the hope that it will be useful, but WITHOUT
13 #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15 #  more details.
16 #
17 #  You should have received a copy of the GNU General Public License along with
18 #  this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 """GStreamer Debug Viewer GUI module."""
21
22 ZOOM_FACTOR = 1.15
23
24
25 def _(s):
26     return s
27
28 import os.path
29 from bisect import bisect_right, bisect_left
30 import logging
31
32 from gi.repository import GObject
33 from gi.repository import Gtk
34 from gi.repository import Gdk
35 from gi.repository import GLib
36
37 from GstDebugViewer import Common, Data, Main
38 from GstDebugViewer.GUI.columns import LineViewColumnManager, ViewColumnManager
39 from GstDebugViewer.GUI.filters import (CategoryFilter,
40                                         DebugLevelFilter,
41                                         FilenameFilter,
42                                         FunctionFilter,
43                                         ThreadFilter,
44                                         ObjectFilter)
45 from GstDebugViewer.GUI.models import (FilteredLogModel,
46                                        LazyLogModel,
47                                        LineViewLogModel,
48                                        LogModelBase)
49
50
51 def action(func):
52
53     func.is_action_handler = True
54
55     return func
56
57
58 def iter_actions(manager):
59
60     cls = type(manager)
61     it = cls.__dict__.iteritems()
62     for name, member in it:
63         try:
64             member.is_action_handler
65         except AttributeError:
66             continue
67
68         bound_method = getattr(manager, name)
69
70         assert name.startswith("handle_")
71         assert name.endswith("_action_activate")
72         action_name = name[len("handle_"):-len("_action_activate")]
73         action_name = action_name.replace("_", "-")
74
75         yield (action_name, bound_method,)
76
77
78 class LineView (object):
79
80     def __init__(self):
81
82         self.column_manager = LineViewColumnManager()
83
84     def attach(self, window):
85
86         for action_name, handler in iter_actions(self):
87             action = getattr(window.actions, action_name)
88             action.connect("activate", handler)
89
90         self.clear_action = window.actions.clear_line_view
91
92         self.line_view = window.widgets.line_view
93         self.line_view.connect(
94             "row-activated", self.handle_line_view_row_activated)
95
96         ui = window.ui_manager
97         self.popup = ui.get_widget(
98             "/ui/context/LineViewContextMenu").get_submenu()
99         Common.GUI.widget_add_popup_menu(self.line_view, self.popup)
100
101         self.log_view = log_view = window.log_view
102         log_view.connect("row-activated", self.handle_log_view_row_activated)
103         sel = log_view.get_selection()
104         sel.connect("changed", self.handle_log_view_selection_changed)
105
106         self.clear_action.props.sensitive = False
107         self.column_manager.attach(window)
108
109     def clear(self):
110
111         model = self.line_view.get_model()
112
113         if len(model) == 0:
114             return
115
116         for i in range(1, len(model)):
117             model.remove_line(1)
118
119         self.clear_action.props.sensitive = False
120
121     def handle_attach_log_file(self, window):
122
123         self.line_view.set_model(LineViewLogModel(window.log_model))
124
125     def handle_line_view_row_activated(self, view, path, column):
126
127         line_index = path[0]
128         line_model = view.get_model()
129         log_model = self.log_view.get_model()
130         super_index = line_model.line_index_to_super(line_index)
131         log_index = log_model.line_index_from_super(super_index)
132         path = (log_index,)
133         self.log_view.scroll_to_cell(path, use_align=True, row_align=.5)
134         sel = self.log_view.get_selection()
135         sel.select_path(path)
136
137     def handle_log_view_row_activated(self, view, path, column):
138
139         log_model = view.get_model()
140         line_index = path[0]
141
142         super_index = log_model.line_index_to_super(line_index)
143         line_model = self.line_view.get_model()
144         if line_model is None:
145             return
146
147         if len(line_model):
148             timestamps = [row[line_model.COL_TIME] for row in line_model]
149             row = log_model[(line_index,)]
150             position = bisect_right(timestamps, row[line_model.COL_TIME])
151         else:
152             position = 0
153         if len(line_model) > 1:
154             other_index = line_model.line_index_to_super(position - 1)
155         else:
156             other_index = -1
157         if other_index == super_index and position != 1:
158             # Already have the line.
159             pass
160         else:
161             line_model.insert_line(position, super_index)
162             self.clear_action.props.sensitive = True
163
164     def handle_log_view_selection_changed(self, selection):
165
166         line_model = self.line_view.get_model()
167         if line_model is None:
168             return
169
170         model, tree_iter = selection.get_selected()
171
172         if tree_iter is None:
173             return
174
175         path = model.get_path(tree_iter)
176         line_index = model.line_index_to_super(path[0])
177
178         if len(line_model) == 0:
179             line_model.insert_line(0, line_index)
180         else:
181             line_model.replace_line(0, line_index)
182
183     @action
184     def handle_clear_line_view_action_activate(self, action):
185
186         self.clear()
187
188
189 class ProgressDialog (object):
190
191     def __init__(self, window, title=""):
192
193         bar = Gtk.InfoBar()
194         bar.props.message_type = Gtk.MessageType.INFO
195         bar.connect("response", self.__handle_info_bar_response)
196         bar.add_button(Gtk.STOCK_CANCEL, 1)
197         area_box = bar.get_content_area()
198         box = Gtk.HBox(spacing=8)
199
200         box.pack_start(Gtk.Label(label=title), False, False, 0)
201
202         progress = Gtk.ProgressBar()
203         box.pack_start(progress, False, False, 0)
204
205         area_box.pack_start(box, False, False, 0)
206
207         self.widget = bar
208         self.__progress_bar = progress
209
210     def __handle_info_bar_response(self, info_bar, response):
211
212         self.handle_cancel()
213
214     def handle_cancel(self):
215
216         pass
217
218     def update(self, progress):
219
220         if self.__progress_bar is None:
221             return
222
223         self.__progress_bar.props.fraction = progress
224
225
226 class Window (object):
227
228     def __init__(self, app):
229
230         self.logger = logging.getLogger("ui.window")
231         self.app = app
232
233         self.dispatcher = None
234         self.info_widget = None
235         self.progress_dialog = None
236         self.update_progress_id = None
237
238         self.window_state = Common.GUI.WindowState()
239         self.column_manager = ViewColumnManager(app.state_section)
240
241         self.actions = Common.GUI.Actions()
242
243         group = Gtk.ActionGroup("MenuActions")
244         group.add_actions([("AppMenuAction", None, _("_Application")),
245                          ("ViewMenuAction", None, _("_View")),
246             ("ViewColumnsMenuAction", None, _("_Columns")),
247             ("HelpMenuAction", None, _("_Help")),
248             ("LineViewContextMenuAction", None, "")])
249         self.actions.add_group(group)
250
251         group = Gtk.ActionGroup("WindowActions")
252         group.add_actions(
253             [("new-window", Gtk.STOCK_NEW, _("_New Window"), "<Ctrl>N"),
254              ("open-file", Gtk.STOCK_OPEN, _(
255               "_Open File"), "<Ctrl>O"),
256              ("reload-file", Gtk.STOCK_REFRESH, _(
257               "_Reload File"), "<Ctrl>R"),
258              ("close-window", Gtk.STOCK_CLOSE, _(
259               "Close _Window"), "<Ctrl>W"),
260              ("cancel-load", Gtk.STOCK_CANCEL, None,),
261              ("clear-line-view", Gtk.STOCK_CLEAR, None),
262              ("show-about", None, _(
263               "About GStreamer Debug Viewer",)),
264              ("enlarge-text", Gtk.STOCK_ZOOM_IN, _(
265               "Enlarge Text"), "<Ctrl>plus"),
266              ("shrink-text", Gtk.STOCK_ZOOM_OUT, _(
267               "Shrink Text"), "<Ctrl>minus"),
268              ("reset-text", Gtk.STOCK_ZOOM_100, _("Normal Text Size"), "<Ctrl>0")])
269         self.actions.add_group(group)
270         self.actions.reload_file.props.sensitive = False
271
272         group = Gtk.ActionGroup("RowActions")
273         group.add_actions(
274             [("hide-before-line", None, _("Hide lines before this point")),
275              ("hide-after-line", None, _(
276               "Hide lines after this point")),
277              ("show-hidden-lines", None, _(
278               "Show hidden lines")),
279              ("edit-copy-line", Gtk.STOCK_COPY, _(
280               "Copy line"), "<Ctrl>C"),
281              ("edit-copy-message", Gtk.STOCK_COPY, _(
282               "Copy message"), ""),
283              ("set-base-time", None, _("Set base time")),
284              ("hide-log-level", None, _("Hide log level")),
285              ("hide-log-level-and-above", None, _(
286               "Hide this log level and above")),
287              ("show-only-log-level", None, _(
288               "Show only log level")),
289              ("hide-log-category", None, _(
290               "Hide log category")),
291              ("show-only-log-category", None, _(
292               "Show only log category")),
293              ("hide-thread", None, _(
294               "Hide thread")),
295              ("show-only-thread", None, _(
296               "Show only thread")),
297              ("hide-object", None, _("Hide object")),
298              ("show-only-object", None, _(
299               "Show only object")),
300              ("hide-function", None, _("Hide function")),
301              ("show-only-function", None, _(
302               "Show only function")),
303              ("hide-filename", None, _("Hide filename")),
304              ("show-only-filename", None, _("Show only filename"))])
305         group.props.sensitive = False
306         self.actions.add_group(group)
307
308         self.actions.add_group(self.column_manager.action_group)
309
310         self.log_file = None
311         self.log_model = None
312         self.log_filter = None
313
314         self.widget_factory = Common.GUI.WidgetFactory(Main.Paths.data_dir)
315         self.widgets = self.widget_factory.make(
316             "main-window.ui", "main_window")
317
318         ui_filename = os.path.join(Main.Paths.data_dir, "menus.ui")
319         self.ui_factory = Common.GUI.UIFactory(ui_filename, self.actions)
320
321         self.ui_manager = ui = self.ui_factory.make()
322         menubar = ui.get_widget("/ui/menubar")
323         self.widgets.vbox_main.pack_start(menubar, False, False, 0)
324
325         self.gtk_window = self.widgets.main_window
326         self.gtk_window.add_accel_group(ui.get_accel_group())
327         self.log_view = self.widgets.log_view
328         self.log_view.drag_dest_unset()
329         self.log_view.set_search_column(-1)
330         sel = self.log_view.get_selection()
331         sel.connect("changed", self.handle_log_view_selection_changed)
332
333         self.view_popup = ui.get_widget(
334             "/ui/context/LogViewContextMenu").get_submenu()
335         Common.GUI.widget_add_popup_menu(self.log_view, self.view_popup)
336
337         # Widgets to set insensitive when the window is considered as
338         # such. This is done during loading/filtering, where we can't set the
339         # whole window insensitive because the progress info bar should be
340         # usable to allow cancellation.
341         self.main_sensitivity = [menubar]
342         self.main_sensitivity.extend(self.widgets.vbox_main.get_children())
343
344         self.line_view = LineView()
345
346         self.attach()
347         self.column_manager.attach(self.log_view)
348
349     def setup_model(self, model):
350
351         self.log_model = model
352         self.log_filter = FilteredLogModel(self.log_model)
353         self.log_filter.handle_process_finished = self.handle_log_filter_process_finished
354
355     def get_top_attach_point(self):
356
357         return self.widgets.vbox_main
358
359     def get_side_attach_point(self):
360
361         return self.widgets.hbox_view
362
363     def attach(self):
364
365         self.zoom_level = 0
366         zoom_percent = self.app.state_section.zoom_level
367         if zoom_percent:
368             self.restore_zoom(float(zoom_percent) / 100.)
369
370         self.window_state.attach(window=self.gtk_window,
371                                  state=self.app.state_section)
372
373         self.clipboard = Gtk.Clipboard.get_for_display(
374             self.gtk_window.get_display(),
375             Gdk.SELECTION_CLIPBOARD)
376
377         for action_name, handler in iter_actions(self):
378             action = getattr(self.actions, action_name)
379             action.connect("activate", handler)
380
381         self.gtk_window.connect(
382             "delete-event", self.handle_window_delete_event)
383
384         self.features = []
385
386         for plugin_feature in self.app.iter_plugin_features():
387             feature = plugin_feature(self.app)
388             self.features.append(feature)
389
390         for feature in self.features:
391             feature.handle_attach_window(self)
392
393         # FIXME: With multiple selection mode, browsing the list with key
394         # up/down slows to a crawl! WTF is wrong with this stupid widget???
395         sel = self.log_view.get_selection()
396         sel.set_mode(Gtk.SelectionMode.BROWSE)
397
398         self.line_view.attach(self)
399
400         # Do not translate; fallback application name for e.g. gnome-shell if
401         # the desktop file is not installed:
402         self.gtk_window.set_wmclass(
403             "gst-debug-viewer", "GStreamer Debug Viewer")
404
405         self.gtk_window.show()
406
407     def detach(self):
408
409         self.set_log_file(None)
410         for feature in self.features:
411             feature.handle_detach_window(self)
412
413         self.window_state.detach()
414         self.column_manager.detach()
415
416     def get_active_line_index(self):
417
418         selection = self.log_view.get_selection()
419         model, tree_iter = selection.get_selected()
420         if tree_iter is None:
421             raise ValueError("no line selected")
422         path = model.get_path(tree_iter)
423         return path[0]
424
425     def get_active_line(self):
426
427         selection = self.log_view.get_selection()
428         model, tree_iter = selection.get_selected()
429         if tree_iter is None:
430             raise ValueError("no line selected")
431         model = self.log_view.get_model()
432         return model.get(tree_iter, *LogModelBase.column_ids)
433
434     def close(self, *a, **kw):
435
436         self.logger.debug("closing window, detaching")
437         self.detach()
438         self.gtk_window.hide()
439         self.logger.debug("requesting close from app")
440         self.app.close_window(self)
441
442     def push_view_state(self):
443
444         self.default_index = None
445         self.default_start_index = None
446
447         model = self.log_view.get_model()
448         if model is None:
449             return
450
451         try:
452             line_index = self.get_active_line_index()
453         except ValueError:
454             super_index = None
455             self.logger.debug("no line selected")
456         else:
457             super_index = model.line_index_to_super(line_index)
458             self.logger.debug("pushing selected line %i (abs %i)",
459                               line_index, super_index)
460
461         self.default_index = super_index
462
463         vis_range = self.log_view.get_visible_range()
464         if vis_range is not None:
465             start_path, end_path = vis_range
466             start_index = start_path[0]
467             self.default_start_index = model.line_index_to_super(start_index)
468
469     def update_model(self, model=None):
470
471         if model is None:
472             model = self.log_view.get_model()
473
474         previous_model = self.log_view.get_model()
475
476         if previous_model == model:
477             # Force update.
478             self.log_view.set_model(None)
479         self.log_view.set_model(model)
480
481     def pop_view_state(self, scroll_to_selection=False):
482
483         model = self.log_view.get_model()
484         if model is None:
485             return
486
487         selected_index = self.default_index
488         start_index = self.default_start_index
489
490         if selected_index is not None:
491
492             try:
493                 select_index = model.line_index_from_super(selected_index)
494             except IndexError as exc:
495                 self.logger.debug(
496                     "abs line index %i filtered out, not reselecting",
497                     selected_index)
498             else:
499                 assert select_index >= 0
500                 sel = self.log_view.get_selection()
501                 path = (select_index,)
502                 sel.select_path(path)
503
504                 if start_index is None or scroll_to_selection:
505                     self.log_view.scroll_to_cell(
506                         path, use_align=True, row_align=.5)
507
508         if start_index is not None and not scroll_to_selection:
509
510             def traverse():
511                 for i in xrange(start_index, len(model)):
512                     yield i
513                 for i in xrange(start_index - 1, 0, -1):
514                     yield i
515             for current_index in traverse():
516                 try:
517                     target_index = model.line_index_from_super(current_index)
518                 except IndexError:
519                     continue
520                 else:
521                     path = (target_index,)
522                     self.log_view.scroll_to_cell(
523                         path, use_align=True, row_align=0.)
524                     break
525
526     def update_view(self):
527
528         view = self.log_view
529         model = view.get_model()
530
531         start_path, end_path = view.get_visible_range()
532         start_index, end_index = start_path[0], end_path[0]
533
534         for line_index in range(start_index, end_index + 1):
535             path = (line_index,)
536             tree_iter = model.get_iter(path)
537             model.row_changed(path, tree_iter)
538
539     def handle_log_view_selection_changed(self, selection):
540
541         try:
542             line_index = self.get_active_line_index()
543         except ValueError:
544             first_selected = True
545             last_selected = True
546         else:
547             first_selected = (line_index == 0)
548             last_selected = (
549                 line_index == len(self.log_view.get_model()) - 1)
550
551         self.actions.hide_before_line.props.sensitive = not first_selected
552         self.actions.hide_after_line.props.sensitive = not last_selected
553
554     def handle_window_delete_event(self, window, event):
555
556         self.actions.close_window.activate()
557
558         return True
559
560     @action
561     def handle_new_window_action_activate(self, action):
562
563         self.app.open_window()
564
565     @action
566     def handle_open_file_action_activate(self, action):
567
568         dialog = Gtk.FileChooserDialog(None, self.gtk_window,
569                                        Gtk.FileChooserAction.OPEN,
570                                       (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
571                                        Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT,))
572         response = dialog.run()
573         dialog.hide()
574         if response == Gtk.ResponseType.ACCEPT:
575             self.set_log_file(dialog.get_filename())
576         dialog.destroy()
577
578     @action
579     def handle_reload_file_action_activate(self, action):
580
581         if self.log_file is None:
582             return
583
584         self.set_log_file(self.log_file.path)
585
586     @action
587     def handle_cancel_load_action_activate(self, action):
588
589         self.logger.debug("cancelling data load")
590
591         self.set_log_file(None)
592
593         if self.progress_dialog is not None:
594             self.hide_info()
595             self.progress_dialog = None
596         if self.update_progress_id is not None:
597             GObject.source_remove(self.update_progress_id)
598             self.update_progress_id = None
599
600         self.set_sensitive(True)
601
602     @action
603     def handle_close_window_action_activate(self, action):
604
605         self.close()
606
607     @action
608     def handle_hide_after_line_action_activate(self, action):
609
610         self.hide_range(after=True)
611
612     @action
613     def handle_hide_before_line_action_activate(self, action):
614
615         self.hide_range(after=False)
616
617     def hide_range(self, after):
618
619         model = self.log_view.get_model()
620         try:
621             filtered_line_index = self.get_active_line_index()
622         except ValueError:
623             return
624
625         if after:
626             first_index = model.line_index_to_super(0)
627             last_index = model.line_index_to_super(filtered_line_index)
628
629             self.logger.info(
630                 "hiding lines after %i (abs %i), first line is abs %i",
631                 filtered_line_index,
632                 last_index,
633                 first_index)
634         else:
635             first_index = model.line_index_to_super(filtered_line_index)
636             last_index = model.line_index_to_super(len(model) - 1)
637
638             self.logger.info(
639                 "hiding lines before %i (abs %i), last line is abs %i",
640                 filtered_line_index,
641                 first_index,
642                 last_index)
643
644         self.push_view_state()
645         start_index = first_index
646         stop_index = last_index + 1
647         self.log_filter.set_range(start_index, stop_index)
648         self.update_model()
649         self.pop_view_state()
650         self.actions.show_hidden_lines.props.sensitive = True
651
652     def get_range(self):
653
654         view = self.log_view
655         model = view.get_model()
656         visible_range = view.get_visible_range()
657         if visible_range is None:
658             return None
659         start_path, end_path = visible_range
660         if not start_path or not end_path:
661             return None
662         ts1 = model.get_value(model.get_iter(start_path),
663                               model.COL_TIME)
664         ts2 = model.get_value(model.get_iter(end_path),
665                               model.COL_TIME)
666         return (ts1, ts2)
667
668     @action
669     def handle_show_hidden_lines_action_activate(self, action):
670
671         self.logger.info("restoring model filter to show all lines")
672         self.push_view_state()
673         self.log_view.set_model(None)
674         self.log_filter.reset()
675         self.update_model(self.log_filter)
676         self.pop_view_state(scroll_to_selection=True)
677         self.actions.show_hidden_lines.props.sensitive = False
678
679     @action
680     def handle_edit_copy_line_action_activate(self, action):
681
682         line_index = self.get_active_line_index()
683         model = self.log_view.get_model()
684         line_offset = model.line_offsets[line_index]
685
686         line_text = model.access_offset(line_offset).strip()
687         line_text = Data.strip_escape(line_text)
688
689         self.clipboard.set_text(line_text)
690
691     @action
692     def handle_edit_copy_message_action_activate(self, action):
693
694         col_id = LogModelBase.COL_MESSAGE
695         self.clipboard.set_text(self.get_active_line()[col_id])
696
697     @action
698     def handle_enlarge_text_action_activate(self, action):
699
700         self.update_zoom_level(1)
701
702     @action
703     def handle_shrink_text_action_activate(self, action):
704
705         self.update_zoom_level(-1)
706
707     @action
708     def handle_reset_text_action_activate(self, action):
709
710         self.update_zoom_level(-self.zoom_level)
711
712     def restore_zoom(self, scale):
713
714         from math import log
715
716         self.zoom_level = int(round(log(scale) / log(ZOOM_FACTOR)))
717
718         self.column_manager.set_zoom(scale)
719
720     def update_zoom_level(self, delta_step):
721
722         if not delta_step:
723             return
724
725         self.zoom_level += delta_step
726         scale = ZOOM_FACTOR ** self.zoom_level
727
728         self.column_manager.set_zoom(scale)
729
730         self.app.state_section.zoom_level = int(round(scale * 100.))
731
732     def set_sensitive(self, sensitive):
733
734         for widget in self.main_sensitivity:
735             widget.props.sensitive = sensitive
736
737     def show_info(self, widget):
738
739         self.hide_info()
740
741         box = self.widgets.vbox_main
742         box.pack_start(widget, False, False, 0)
743         box.reorder_child(widget, 2)
744         widget.show_all()
745         self.info_widget = widget
746
747     def hide_info(self):
748
749         if self.info_widget is None:
750             return
751
752         self.info_widget.destroy()
753         self.info_widget = None
754
755     def add_model_filter(self, filter):
756
757         self.progress_dialog = ProgressDialog(self, _("Filtering"))
758         self.show_info(self.progress_dialog.widget)
759         self.progress_dialog.handle_cancel = self.handle_filter_progress_dialog_cancel
760         dispatcher = Common.Data.GSourceDispatcher()
761
762         # FIXME: Unsetting the model to keep e.g. the dispatched timeline
763         # sentinel from collecting data while we filter idly, which slows
764         # things down for nothing.
765         self.push_view_state()
766         self.log_view.set_model(None)
767         self.log_filter.add_filter(filter, dispatcher=dispatcher)
768
769         GObject.timeout_add(250, self.update_filter_progress)
770
771         self.set_sensitive(False)
772
773     def update_filter_progress(self):
774
775         if self.progress_dialog is None:
776             return False
777
778         try:
779             progress = self.log_filter.get_filter_progress()
780         except ValueError:
781             self.logger.warning("no filter process running")
782             return False
783
784         self.progress_dialog.update(progress)
785
786         return True
787
788     def handle_filter_progress_dialog_cancel(self):
789
790         self.hide_info()
791         self.progress_dialog = None
792
793         self.log_filter.abort_process()
794         self.log_view.set_model(self.log_filter)
795         self.pop_view_state()
796
797         self.set_sensitive(True)
798
799     def handle_log_filter_process_finished(self):
800
801         self.hide_info()
802         self.progress_dialog = None
803
804         # No push_view_state here, did this in add_model_filter.
805         self.update_model(self.log_filter)
806         self.pop_view_state()
807
808         self.actions.show_hidden_lines.props.sensitive = True
809
810         self.set_sensitive(True)
811
812     @action
813     def handle_set_base_time_action_activate(self, action):
814
815         row = self.get_active_line()
816         self.column_manager.set_base_time(row[LogModelBase.COL_TIME])
817
818     @action
819     def handle_hide_log_level_action_activate(self, action):
820
821         row = self.get_active_line()
822         debug_level = row[LogModelBase.COL_LEVEL]
823         self.add_model_filter(DebugLevelFilter(debug_level))
824
825     @action
826     def handle_hide_log_category_action_activate(self, action):
827
828         row = self.get_active_line()
829         category = row[LogModelBase.COL_CATEGORY]
830         self.add_model_filter(CategoryFilter(category))
831
832     @action
833     def handle_hide_thread_action_activate(self, action):
834
835         row = self.get_active_line()
836         thread = row[LogModelBase.COL_THREAD]
837         self.add_model_filter(ThreadFilter(thread))
838
839     @action
840     def handle_hide_object_action_activate(self, action):
841
842         row = self.get_active_line()
843         object_ = row[LogModelBase.COL_OBJECT]
844         self.add_model_filter(ObjectFilter(object_))
845
846     @action
847     def handle_hide_function_action_activate(self, action):
848
849         row = self.get_active_line()
850         object_ = row[LogModelBase.COL_FUNCTION]
851         self.add_model_filter(FunctionFilter(object_))
852
853     @action
854     def handle_hide_filename_action_activate(self, action):
855
856         row = self.get_active_line()
857         filename = row[LogModelBase.COL_FILENAME]
858         self.add_model_filter(FilenameFilter(filename))
859
860     @action
861     def handle_hide_log_level_and_above_action_activate(self, action):
862
863         row = self.get_active_line()
864         debug_level = row[LogModelBase.COL_LEVEL]
865         self.add_model_filter(
866             DebugLevelFilter(debug_level, DebugLevelFilter.this_and_above))
867
868     @action
869     def handle_show_only_log_level_action_activate(self, action):
870
871         row = self.get_active_line()
872         debug_level = row[LogModelBase.COL_LEVEL]
873         self.add_model_filter(
874             DebugLevelFilter(debug_level, DebugLevelFilter.all_but_this))
875
876     @action
877     def handle_show_only_log_category_action_activate(self, action):
878
879         row = self.get_active_line()
880         category = row[LogModelBase.COL_CATEGORY]
881         self.add_model_filter(CategoryFilter(category, True))
882
883     @action
884     def handle_show_only_thread_action_activate(self, action):
885
886         row = self.get_active_line()
887         thread = row[LogModelBase.COL_THREAD]
888         self.add_model_filter(ThreadFilter(thread, True))
889
890     @action
891     def handle_show_only_object_action_activate(self, action):
892
893         row = self.get_active_line()
894         object_ = row[LogModelBase.COL_OBJECT]
895         self.add_model_filter(ObjectFilter(object_, True))
896
897     @action
898     def handle_show_only_function_action_activate(self, action):
899
900         row = self.get_active_line()
901         object_ = row[LogModelBase.COL_FUNCTION]
902         self.add_model_filter(FunctionFilter(object_, True))
903
904     @action
905     def handle_show_only_filename_action_activate(self, action):
906
907         row = self.get_active_line()
908         filename = row[LogModelBase.COL_FILENAME]
909         self.add_model_filter(FilenameFilter(filename, True))
910
911     @action
912     def handle_show_about_action_activate(self, action):
913
914         from GstDebugViewer import version
915
916         dialog = self.widget_factory.make_one(
917             "about-dialog.ui", "about_dialog")
918         dialog.props.version = version
919         dialog.run()
920         dialog.destroy()
921
922     @staticmethod
923     def _timestamp_cell_data_func(column, renderer, model, tree_iter):
924
925         ts = model.get_value(tree_iter, LogModel.COL_TIME)
926         renderer.props.text = Data.time_args(ts)
927
928     def _message_cell_data_func(self, column, renderer, model, tree_iter):
929
930         offset = model.get_value(tree_iter, LogModel.COL_MESSAGE_OFFSET)
931         self.log_file.seek(offset)
932         renderer.props.text = strip_escape(self.log_file.readline().strip())
933
934     def set_log_file(self, filename):
935
936         if self.log_file is not None:
937             for feature in self.features:
938                 feature.handle_detach_log_file(self, self.log_file)
939
940         if filename is None:
941             if self.dispatcher is not None:
942                 self.dispatcher.cancel()
943             self.dispatcher = None
944             self.log_file = None
945             self.actions.groups["RowActions"].props.sensitive = False
946         else:
947             self.logger.debug("setting log file %r", filename)
948
949             try:
950                 self.setup_model(LazyLogModel())
951
952                 self.dispatcher = Common.Data.GSourceDispatcher()
953                 self.log_file = Data.LogFile(filename, self.dispatcher)
954             except EnvironmentError as exc:
955                 try:
956                     file_size = os.path.getsize(filename)
957                 except EnvironmentError:
958                     pass
959                 else:
960                     if file_size == 0:
961                         # Trying to mmap an empty file results in an invalid
962                         # argument error.
963                         self.show_error(_("Could not open file"),
964                                         _("The selected file is empty"))
965                         return
966                 self.handle_environment_error(exc, filename)
967                 return
968
969             basename = os.path.basename(filename)
970             self.gtk_window.props.title = _(
971                 "%s - GStreamer Debug Viewer") % (basename,)
972
973             self.log_file.consumers.append(self)
974             self.log_file.start_loading()
975
976     def handle_environment_error(self, exc, filename):
977
978         self.show_error(_("Could not open file"), str(exc))
979
980     def show_error(self, message1, message2):
981
982         bar = Gtk.InfoBar()
983         bar.props.message_type = Gtk.MessageType.ERROR
984         box = bar.get_content_area()
985
986         markup = "<b>%s</b> %s" % (GLib.markup_escape_text(message1),
987                                    GLib.markup_escape_text(message2),)
988         label = Gtk.Label()
989         label.props.use_markup = True
990         label.props.label = markup
991         label.props.selectable = True
992         box.pack_start(label, False, False, 0)
993
994         self.show_info(bar)
995
996     def handle_load_started(self):
997
998         self.logger.debug("load has started")
999
1000         self.progress_dialog = ProgressDialog(self, _("Loading log file"))
1001         self.show_info(self.progress_dialog.widget)
1002         self.progress_dialog.handle_cancel = self.handle_load_progress_dialog_cancel
1003         self.update_progress_id = GObject.timeout_add(
1004             250, self.update_load_progress)
1005
1006         self.set_sensitive(False)
1007
1008     def handle_load_progress_dialog_cancel(self):
1009
1010         self.actions.cancel_load.activate()
1011
1012     def update_load_progress(self):
1013
1014         if self.progress_dialog is None:
1015             self.logger.debug(
1016                 "progress dialog is gone, removing progress update timeout")
1017             self.update_progress_id = None
1018             return False
1019
1020         progress = self.log_file.get_load_progress()
1021         self.progress_dialog.update(progress)
1022
1023         return True
1024
1025     def handle_load_finished(self):
1026
1027         self.logger.debug("load has finshed")
1028
1029         self.hide_info()
1030         self.progress_dialog = None
1031
1032         self.log_model.set_log(self.log_file)
1033         self.log_filter.reset()
1034
1035         self.actions.reload_file.props.sensitive = True
1036         self.actions.groups["RowActions"].props.sensitive = True
1037         self.actions.show_hidden_lines.props.sensitive = False
1038
1039         self.set_sensitive(True)
1040
1041         if len(self.log_model) == 0:
1042             self.show_error(
1043                 _("The file does not contain any parsable lines."),
1044                 _("It is not a GStreamer log file."))
1045
1046         def idle_set():
1047             self.logger.debug("idle trigger after load finished")
1048             self.log_view.set_model(self.log_filter)
1049
1050             self.line_view.handle_attach_log_file(self)
1051             for feature in self.features:
1052                 feature.handle_attach_log_file(self, self.log_file)
1053             if len(self.log_filter):
1054                 sel = self.log_view.get_selection()
1055                 sel.select_path((0,))
1056             return False
1057
1058         GObject.idle_add(idle_set)