[gnome-sound-recorder/bilelmoussaoui/refresh] Add a player
- From: Bilal Elmoussaoui <bilelmoussaoui src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-sound-recorder/bilelmoussaoui/refresh] Add a player
- Date: Sun, 16 Jun 2019 19:48:20 +0000 (UTC)
commit 6af365513843f8b67379ad00ba1f2d55b913f999
Author: Bilal Elmoussaoui <bil elmoussaoui gmail com>
Date: Sun Jun 16 21:47:55 2019 +0200
Add a player
data/org.gnome.SoundRecorder.data.gresource.xml | 1 +
data/ui/main_window.ui | 113 ++++++++++------
data/ui/player.ui | 164 ++++++++++++++++++++++++
data/ui/recording_row.ui | 6 +-
src/mainWindow.js | 24 +++-
src/player.js | 103 ++++++++-------
src/recording.js | 88 +++++--------
7 files changed, 354 insertions(+), 145 deletions(-)
---
diff --git a/data/org.gnome.SoundRecorder.data.gresource.xml b/data/org.gnome.SoundRecorder.data.gresource.xml
index be02925..bb16711 100644
--- a/data/org.gnome.SoundRecorder.data.gresource.xml
+++ b/data/org.gnome.SoundRecorder.data.gresource.xml
@@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/org/gnome/SoundRecorder">
<file>application.css</file>
+ <file compressed="true" preprocess="xml-stripblanks" alias="player.ui">ui/player.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="main_window.ui">ui/main_window.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="recording_row.ui">ui/recording_row.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="recording_waveform_row.ui">ui/recording_waveform_row.ui</file>
diff --git a/data/ui/main_window.ui b/data/ui/main_window.ui
index 6c9c651..69573ae 100644
--- a/data/ui/main_window.ui
+++ b/data/ui/main_window.ui
@@ -4,8 +4,8 @@
<requires lib="gtk+" version="3.20"/>
<requires lib="libhandy" version="0.0"/>
<template class="Gjs_MainWindow" parent="GtkApplicationWindow">
- <property name="width_request">480</property>
- <property name="height_request">640</property>
+ <property name="width_request">360</property>
+ <property name="height_request">600</property>
<property name="can_focus">False</property>
<property name="default_width">480</property>
<property name="default_height">780</property>
@@ -144,66 +144,101 @@
</packing>
</child>
<child>
- <object class="GtkScrolledWindow">
+ <object class="GtkBox">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="shadow_type">in</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
<child>
- <object class="GtkViewport">
+ <object class="GtkScrolledWindow">
<property name="visible">True</property>
- <property name="can_focus">False</property>
+ <property name="can_focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
<child>
- <object class="HdyColumn">
+ <object class="GtkViewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="maximum_width">760</property>
- <property name="linear_growth_width">760</property>
+ <property name="vexpand">True</property>
<child>
- <object class="GtkBox">
+ <object class="HdyColumn">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="valign">start</property>
<property name="vexpand">True</property>
- <property name="border_width">18</property>
- <property name="orientation">vertical</property>
+ <property name="maximum_width">760</property>
+ <property name="linear_growth_width">760</property>
<child>
- <object class="GtkRevealer" id="new_recording_revealer">
+ <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
- <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
<child>
- <placeholder/>
+ <object class="GtkRevealer" id="new_recording_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="records_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="vexpand">True</property>
+ <property name="selection_mode">none</property>
+ <style>
+ <class name="frame"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
</child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkListBox" id="records_listbox">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="valign">start</property>
- <property name="vexpand">True</property>
- <property name="selection_mode">none</property>
- <style>
- <class name="frame"/>
- </style>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
</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="GtkRevealer" id="player_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="vexpand">False</property>
+ <property name="transition_type">slide-up</property>
+ <property name="reveal_child">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
</child>
</object>
<packing>
diff --git a/data/ui/player.ui b/data/ui/player.ui
new file mode 100644
index 0000000..0c4de46
--- /dev/null
+++ b/data/ui/player.ui
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkAdjustment" id="player_adjustement">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <template class="Gjs_PlayerWidget" parent="GtkBox">
+ <property name="height_request">60</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="clip_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="clip_duration_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale" id="player_scale">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">player_adjustement</property>
+ <property name="fill_level">0</property>
+ <property name="round_digits">1</property>
+ <property name="draw_value">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="hexpand">False</property>
+ <child>
+ <object class="GtkButton" id="backward_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-seek-backward-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="pause_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-pause-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="forward_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-seek-forward-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/recording_row.ui b/data/ui/recording_row.ui
index 91dcf2f..e6f12b2 100644
--- a/data/ui/recording_row.ui
+++ b/data/ui/recording_row.ui
@@ -36,7 +36,7 @@
</packing>
</child>
<child>
- <object class="GtkButton" id="pause_button">
+ <object class="GtkButton" id="stop_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@@ -48,12 +48,12 @@
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">center</property>
- <property name="icon_name">media-playback-pause-symbolic</property>
+ <property name="icon_name">media-playback-stop-symbolic</property>
</object>
</child>
</object>
<packing>
- <property name="name">pause</property>
+ <property name="name">stop</property>
<property name="position">1</property>
</packing>
</child>
diff --git a/src/mainWindow.js b/src/mainWindow.js
index abc7409..69e7ccd 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -40,7 +40,8 @@ const NewRecording = imports.recording.NewRecording;
const RecordingRow = imports.recording.RecordingRow;
const Preferences = imports.preferences;
-
+const Player = imports.player.Player;
+const PlayerWidget = imports.player.PlayerWidget;
const Waveform = imports.waveform;
let activeProfile = null;
@@ -114,6 +115,7 @@ var MainWindow = GObject.registerClass({
'record_label',
'records_listbox',
'new_recording_revealer',
+ 'player_revealer',
]
},
class MainWindow extends Gtk.ApplicationWindow {
@@ -121,8 +123,8 @@ var MainWindow = GObject.registerClass({
super._init();
this._addAppMenu();
this._recordingsManager = new RecordingsManager();
+ this._player = new Player();
this._initWidgets();
-
this.show_all();
}
@@ -143,6 +145,15 @@ var MainWindow = GObject.registerClass({
this._newRecordingWidget.updateRecordTime(recordTime);
});
this._new_recording_revealer.add(this._newRecordingWidget);
+
+
+ this._playerWidget = new PlayerWidget();
+ this._player_revealer.add(this._playerWidget);
+ this._player_revealer.set_reveal_child(false);
+
+ this._player.connect("time-updated", (obj, time) => {
+ this._playerWidget.updateTime(time);
+ });
}
@@ -188,6 +199,15 @@ var MainWindow = GObject.registerClass({
_onRecordingAdded(recording) {
this._main_stack.set_visible_child_name('records_view');
let recordingRow = new RecordingRow(recording);
+ recordingRow.connect("play", (obj, recording) => {
+ this._playerWidget.setPlaying(recording);
+ this._player.play(recording);
+ this._player_revealer.set_reveal_child(true);
+ });
+ recordingRow.connect("stop", () => {
+ this._player.stopPlaying();
+ this._player_revealer.set_reveal_child(false);
+ });
this._records_listbox.add(recordingRow);
this._records_listbox.show_all();
}
diff --git a/src/player.js b/src/player.js
index c6606d2..6dce845 100644
--- a/src/player.js
+++ b/src/player.js
@@ -32,6 +32,7 @@ const Mainloop = imports.mainloop;
const Application = imports.application;
const MainWindow = imports.mainWindow;
const Waveform = imports.waveform;
+const utils = imports.utils;
const PipelineStates = {
PLAYING: 0,
@@ -51,7 +52,7 @@ const _TENTH_SEC = 100000000;
var Player = GObject.registerClass({
Signals: {
- 'timer-updated': {
+ 'time-updated': {
flags: GObject.SignalFlags.RUN_FIRST,
param_types: [ GObject.TYPE_INT ]
}
@@ -62,11 +63,11 @@ var Player = GObject.registerClass({
_init() {
super._init();
this.playState = PipelineStates.STOPPED;
- this.play = Gst.ElementFactory.make("playbin", "play");
+ this.playbin = Gst.ElementFactory.make("playbin", "play");
this.sink = Gst.ElementFactory.make("pulsesink", "sink");
- this.play.set_property("audio-sink", this.sink);
- this.clock = this.play.get_clock();
- this.playBus = this.play.get_bus();
+ this.playbin.set_property("audio-sink", this.sink);
+ this.clock = this.playbin.get_clock();
+ this.playBus = this.playbin.get_bus();
this._asset = null;
}
_playPipeline() {
@@ -80,12 +81,13 @@ var Player = GObject.registerClass({
}
get duration() {
- return this.play.query_duration(Gst.Format.TIME)
+ return this.playbin.query_duration(Gst.Format.TIME)
}
- setUri(uri) {
- this.play.set_property("uri", uri);
+ play(recording) {
+ this.playbin.set_property("uri", recording.uri);
+ this.startPlaying();
}
@@ -98,11 +100,11 @@ var Player = GObject.registerClass({
if (this.playState == PipelineStates.PAUSED) {
this.updatePosition();
- this.play.set_base_time(this.clock.get_time());
- this.baseTime = this.play.get_base_time() - this.runTime;
+ this.playbin.set_base_time(this.clock.get_time());
+ this.baseTime = this.playbin.get_base_time() - this.runTime;
}
- this.ret = this.play.set_state(Gst.State.PLAYING);
+ this.ret = this.playbin.set_state(Gst.State.PLAYING);
this.playState = PipelineStates.PLAYING;
if (this.ret == Gst.StateChangeReturn.FAILURE) {
@@ -116,7 +118,7 @@ var Player = GObject.registerClass({
}
pausePlaying() {
- this.play.set_state(Gst.State.PAUSED);
+ this.playbin.set_state(Gst.State.PAUSED);
this.playState = PipelineStates.PAUSED;
if (this.timeout) {
@@ -132,7 +134,7 @@ var Player = GObject.registerClass({
}
onEnd() {
- this.play.set_state(Gst.State.NULL);
+ this.playbin.set_state(Gst.State.NULL);
this.playState = PipelineStates.STOPPED;
this.playBus.remove_signal_watch();
this._updateTime();
@@ -176,7 +178,7 @@ var Player = GObject.registerClass({
case Gst.MessageType.ASYNC_DONE:
if (this.sought) {
- this.play.set_state(this._lastState);
+ this.playbin.set_state(this._lastState);
MainWindow.view.setProgressScaleSensitive();
}
this.updatePosition();
@@ -188,7 +190,7 @@ var Player = GObject.registerClass({
case Gst.MessageType.NEW_CLOCK:
if (this.playState == PipelineStates.PAUSED) {
- this.clock = this.play.get_clock();
+ this.clock = this.playbin.get_clock();
this.startPlaying();
}
break;
@@ -200,44 +202,17 @@ var Player = GObject.registerClass({
}
_updateTime() {
- let time = this.play.query_position(Gst.Format.TIME)[1]/Gst.SECOND;
- this.trackDuration = this.play.query_duration(Gst.Format.TIME)[1];
- this.trackDurationSecs = this.trackDuration/Gst.SECOND;
-
- if (time >= 0 && this.playState != PipelineStates.STOPPED) {
- this.emit("timer-updated", time);
- } else if (time >= 0 && this.playState == PipelineStates.STOPPED) {
- this.emit("timer-updated", 0);
- }
-
- let absoluteTime = 0;
-
- if (this.clock == null) {
- this.clock = this.play.get_clock();
- }
- try {
- absoluteTime = this.clock.get_time();
- } catch(error) {
- // no-op
- }
-
- if (this.baseTime == 0)
- this.baseTime = absoluteTime;
-
- this.runTime = absoluteTime- this.baseTime;
- let approxTime = Math.round(this.runTime/_TENTH_SEC);
-
- if (MainWindow.wave != null) {
- MainWindow.wave._drawEvent(approxTime);
- }
+ let time = this.playbin.query_position(Gst.Format.TIME)[1]/Gst.SECOND;
+ let trackDuration = this.playbin.query_duration(Gst.Format.TIME)[1];
+ this.emit("time-updated", time);
return true;
}
queryPosition() {
let position = 0;
while (position == 0) {
- position = this.play.query_position(Gst.Format.TIME)[1]/Gst.SECOND;
+ position = this.playbin.query_position(Gst.Format.TIME)[1]/Gst.SECOND;
}
return position;
@@ -251,7 +226,7 @@ var Player = GObject.registerClass({
}
setVolume(value) {
- this.play.set_volume(GstAudio.StreamVolumeFormat.CUBIC, value);
+ this.playbin.set_volume(GstAudio.StreamVolumeFormat.CUBIC, value);
}
passSelected(selected) {
@@ -281,3 +256,37 @@ var Player = GObject.registerClass({
}
}
);
+
+
+var PlayerWidget = GObject.registerClass({
+ Template: 'resource:///org/gnome/SoundRecorder/player.ui',
+ Signals: {
+ },
+ InternalChildren: [
+ 'clip_name_label',
+ 'clip_duration_label',
+ 'player_scale',
+ 'player_adjustement'
+ ],
+ },
+ class PlayerWidget extends Gtk.Box {
+ _init() {
+ super._init();
+
+ this.show_all();
+ }
+
+ setPlaying(recording) {
+
+ this._player_adjustement.configure(0, 0, recording.duration, 1, 10, 0);
+ this._clip_name_label.set_text(recording.fileName);
+
+ }
+
+ updateTime(time) {
+ this._clip_duration_label.set_text(utils.getDisplayDuration(time));
+ this._player_adjustement.set_value(time);
+ }
+
+ }
+);
diff --git a/src/recording.js b/src/recording.js
index 8608e74..c297ce7 100644
--- a/src/recording.js
+++ b/src/recording.js
@@ -20,11 +20,12 @@
const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
+
const GObject = imports.gi.GObject;
const Gst = imports.gi.Gst;
const GstPbutils = imports.gi.GstPbutils;
-const Player = imports.player.Player;
+
const utils = imports.utils;
const WaveForm = imports.waveform.WaveForm;
@@ -65,87 +66,66 @@ var Recording = GObject.registerClass({},
}
this.fileName = returnedName;
- let discoverer = new GstPbutils.Discoverer();
- discoverer.start();
- discoverer.discover_uri_async(this.uri);
- discoverer.connect('discovered', (_discoverer, info, error) => {
- let result = info.get_result();
- log(result);
- this._onDiscovererFinished(result, info, error);
- });
+ let d = Gst.parse_launch("filesrc name=source ! fakesink")
+ let source = d.get_by_name("source")
+ source.set_property("location", filePath)
+ d.set_state(Gst.State.PLAYING)
+ this.duration = d.query_duration( Gst.Format.TIME)
+ log(this.duration)
+ d.set_state(Gst.State.NULL)
}
- _onDiscovererFinished(res, info, err) {
-
- if (res == GstPbutils.DiscovererResult.OK) {
-
- this.tagInfo = info.get_tags();
-
- let dateTimeTag = this.tagInfo.get_date_time('datetime')[1];
- let durationInfo = info.get_duration();
- this.duration = durationInfo;
-
- /* this.file.dateCreated will usually be null since time::created it doesn't usually exist.
- Therefore, we prefer to set it with tags */
- if (dateTimeTag != null) {
- this.dateCreated = dateTimeTag.to_g_date_time();;
- }
- /* FIX ME
- this._getCapsForList(info);
- */
- } else {
- // don't index files we can't play
-
- log("File cannot be played");
- }
-
- }
}
);
var RecordingRow = GObject.registerClass({
- Template: 'resource:///org/gnome/SoundRecorder/recording_row.ui',
- InternalChildren: [
- 'clip_label',
- 'created_label',
- 'time_label',
- 'play_button',
- 'pause_button',
- 'overlay',
- 'buttons_stack'
- ],
- Signals: {
- }
+ Template: 'resource:///org/gnome/SoundRecorder/recording_row.ui',
+ InternalChildren: [
+ 'clip_label',
+ 'created_label',
+ 'time_label',
+ 'play_button',
+ 'stop_button',
+ 'overlay',
+ 'buttons_stack'
+ ],
+ Signals: {
+ 'play': {
+ flags: GObject.SignalFlags.RUN_FIRST,
+ param_types: [GObject.Object]
+ },
+ 'stop': {
+ flags: GObject.SignalFlags.RUN_FIRST
+ }
+ }
},
class RecordingRow extends Gtk.ListBoxRow {
_init(recording) {
super._init();
- this._player = new Player();
this.recording = recording;
this._clip_label.set_text(recording.fileName);
this._created_label.set_text(utils.getDisplayTime(recording.dateCreated));
this._overlay.add_overlay(recording.wave);
- this._player.setUri(recording.uri);
-
this._play_button.connect('clicked', () => {
- this._player.startPlaying();
- this._buttons_stack.set_visible_child_name("pause");
+ this.emit("play", recording);
+ this._buttons_stack.set_visible_child_name("stop");
});
- this._pause_button.connect('clicked', () => {
- this._player.pausePlaying();
+ this._stop_button.connect('clicked', () => {
+ this.emit("stop");
this._buttons_stack.set_visible_child_name("play");
});
-
+ /*
this._player.connect("timer-updated", (obj, seconds)=> {
this._time_label.set_text(utils.getDisplayDuration(seconds));
})
+ */
this.show_all();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]