Skip to content

Commit

Permalink
Add Exaile Developer plugin
Browse files Browse the repository at this point in the history
* Probably should integrate with the interactive consoles at some point
* Fixes #326
  • Loading branch information
virtuald committed Apr 29, 2017
1 parent ca3300f commit 42b2225
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 0 deletions.
5 changes: 5 additions & 0 deletions plugins/developer/PLUGININFO
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Version='4.0.0'
Authors=["Dustin Spicuzza <dustin@virtualroadside.com>"]
Name=_("Developer")
Description=_("Provides diagnostic information for Exaile developers")
Category=_('Development')
186 changes: 186 additions & 0 deletions plugins/developer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Copyright (C) 2017 Dustin Spicuzza
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

from __future__ import print_function

from gi.repository import GLib
from gi.repository import Gtk

from xl import (
event,
providers
)
from xl.nls import gettext as _

from xlgui.guiutil import GtkTemplate
from xlgui.widgets import menu

import threading

class DeveloperPlugin(object):
'''
Shows useful information for Exaile developers
'''

def enable(self, exaile):
self.lock = threading.RLock()

self.exaile = exaile
self.window = None
self.menu = None

# Event data storage
self.events = {'__all': 0}
self.capture_events = True

# subscribe for all events
event.add_callback(self.on_events)

def disable(self, exaile):
self.teardown(exaile)

def teardown(self, exaile):
event.remove_callback(self.on_events)
if self.window:
self.window.destroy()
if self.menu:
providers.unregister('menubar-tools-menu', self.menu)

def on_gui_loaded(self):

# add a thing to the view menu
self.menu = menu.simple_menu_item('developer', '', _('Developer Tools'),
callback=self.on_view_menu)

providers.register('menubar-tools-menu', self.menu)

def on_view_menu(self, widget, name, parent, context):
if self.window:
self.window.present()
else:
self.window = DeveloperWindow(self.exaile.gui.main.window, self)

def _delete(w, e):
self.window = None

self.window.connect('delete-event', _delete)
self.window.show_all()

#
# Capture callback stuff
#

def clear_events(self):
with self.lock:
self.events.clear()
self.events['__all'] = 0

def pause_events(self, pause):
with self.lock:
self.capture_events = pause

def on_events(self, etype, *args):
with self.lock:
if not self.capture_events:
return
self.events['__all'] += 1
try:
self.events[etype] += 1
except KeyError:
self.events[etype] = 1

def get_event_data(self, last_count):
with self.lock:
all_count = self.events['__all']
if all_count != last_count:
return self.events.copy(), all_count


plugin_class = DeveloperPlugin


@GtkTemplate('developer_window.ui', relto=__file__)
class DeveloperWindow(Gtk.Window):

__gtype_name__ = 'DeveloperWindow'

event_filter_entry, \
event_model_filter, \
event_tree, \
event_store = GtkTemplate.Child.widgets(4)

def __init__(self, parent, plugin):
Gtk.Window.__init__(self)
self.init_template()

if parent:
self.set_transient_for(parent)

self.events_count = None
self.plugin = plugin
self.event_filter_text = ''

# key: name, value: iter
self.event_model_idx = {}

self.event_model_filter.set_visible_func(self.on_event_filter_row)

self.event_tree.set_search_entry(self.event_filter_entry)
self.event_tree.connect('start-interactive-search',
lambda *a: self.event_filter_entry.grab_focus())

self.event_timeout_id = GLib.timeout_add(250, self.on_event_update)

@GtkTemplate.Callback
def on_clear_events(self, widget):
self.plugin.clear_events()
self.events_count = None
self.event_model_idx.clear()
self.event_store.clear()

@GtkTemplate.Callback
def on_delete(self, widget, event):
GLib.source_remove(self.event_timeout_id)

def on_event_filter_row(self, model, titer, unused):
row = model.get(titer, 0)
if row:
return self.event_filter_text in row[0]

@GtkTemplate.Callback
def on_event_pause_toggle(self, widget):
self.plugin.pause_events(widget.get_active())

@GtkTemplate.Callback
def on_event_filter_entry_changed(self, widget):
self.event_filter_text = widget.get_text().decode('utf-8')
self.event_model_filter.refilter()

def on_event_update(self):
'''Periodically updates the displayed list of events'''
data = self.plugin.get_event_data(self.events_count)
if data:
events, self.events_count = data
for name, count in events.iteritems():
titer = self.event_model_idx.get(name)
if titer:
self.event_store[titer][1] = count
else:
titer = self.event_store.append([name, count])
self.event_model_idx[name] = titer

return True

165 changes: 165 additions & 0 deletions plugins/developer/developer_window.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkListStore" id="event_store">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
<!-- column-name count -->
<column type="gint"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="event_model_filter">
<property name="child_model">event_store</property>
</object>
<object class="GtkTreeModelSort" id="event_model_sort">
<property name="model">event_model_filter</property>
</object>
<template class="DeveloperWindow" parent="GtkWindow">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Exaile Developer Tools</property>
<property name="destroy_with_parent">True</property>
<signal name="delete-event" handler="on_delete" swapped="no"/>
<child>
<object class="GtkNotebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">always</property>
<property name="shadow_type">in</property>
<property name="min_content_height">300</property>
<child>
<object class="GtkTreeView" id="event_tree">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">event_model_sort</property>
<property name="search_column">0</property>
<property name="fixed_height_mode">True</property>
<property name="show_expanders">False</property>
<property name="enable_grid_lines">horizontal</property>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="sort_column_id">0</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Count</property>
<property name="clickable">True</property>
<property name="sort_column_id">1</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkSearchEntry" id="event_filter_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_name">edit-find-symbolic</property>
<property name="primary_icon_activatable">False</property>
<property name="primary_icon_sensitive">False</property>
<property name="placeholder_text" translatable="yes">Filter event types</property>
<signal name="search-changed" handler="on_event_filter_entry_changed" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton">
<property name="label" translatable="yes">Pause</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<signal name="toggled" handler="on_event_pause_toggle" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Clear</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<signal name="clicked" handler="on_clear_events" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Events</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

0 comments on commit 42b2225

Please sign in to comment.