endif
JAVASCRIPT += $(ROOTPATH)/app/chconf.js
JAVASCRIPT += $(ROOTPATH)/app/epg.js
+JAVASCRIPT += $(ROOTPATH)/app/epgevent.js
JAVASCRIPT += $(ROOTPATH)/app/dvr.js
JAVASCRIPT += $(ROOTPATH)/app/epggrab.js
JAVASCRIPT += $(ROOTPATH)/app/config.js
*
* Return 0 if access is granted, -1 otherwise
*/
-static inline int access_verify2(access_t *a, uint32_t mask)
+static inline int access_verify2(const access_t *a, uint32_t mask)
{ return (mask & ACCESS_OR) ?
((a->aa_rights & mask) ? 0 : -1) :
((a->aa_rights & mask) == mask ? 0 : -1); }
}
static htsmsg_t *
-api_epg_entry ( epg_broadcast_t *eb, const char *lang, access_t *perm, const char **blank )
+api_epg_entry ( epg_broadcast_t *eb, const char *lang, const access_t *perm, const char **blank )
{
const char *s, *blank2 = NULL;
char buf[64];
return 0;
}
+static int
+api_epg_sort_by_time_t(const void *a, const void *b, void *arg)
+{
+ const time_t *at= (const time_t*)a;
+ const time_t *bt= (const time_t*)b;
+ return *at - *bt;
+}
+
+/// Generate a sorted list of episodes that
+/// do NOT match ebc_skip in to message l.
+/// @return number of entries allocated.
+static uint32_t
+api_epg_episode_sorted(const struct epg_set *set,
+ const access_t *perm,
+ htsmsg_t *l,
+ const char *lang,
+ const epg_broadcast_t *ebc_skip)
+{
+ typedef struct {
+ time_t start;
+ htsmsg_t *m;
+ } bcast_entry_t;
+
+ epg_broadcast_t *ebc;
+ htsmsg_t *m;
+ bcast_entry_t *bcast_entries = NULL;
+ const epg_set_item_t *item;
+ bcast_entry_t new_bcast_entry;
+ size_t num_allocated = 0;
+ size_t num_entries = 0;
+ size_t i;
+
+ LIST_FOREACH(item, &set->broadcasts, item_link) {
+ ebc = item->broadcast;
+ if (ebc != ebc_skip) {
+ m = api_epg_entry(ebc, lang, perm, NULL);
+ if (num_entries == num_allocated) {
+ num_allocated = MAX(100, num_allocated + 100);
+ /* We don't expect any/many reallocs so we store physical struct instead of pointers */
+ bcast_entries = realloc(bcast_entries, num_allocated * sizeof(bcast_entry_t));
+ }
+
+ new_bcast_entry.start = htsmsg_get_u32_or_default(m, "start", 0);
+ new_bcast_entry.m = m;
+ bcast_entries[num_entries++] = new_bcast_entry;
+ }
+ }
+
+ tvh_qsort_r(bcast_entries, num_entries, sizeof(bcast_entry_t), api_epg_sort_by_time_t, 0);
+
+ for (i=0; i<num_entries; ++i) {
+ htsmsg_t *m = bcast_entries[i].m;
+ htsmsg_add_msg(l, NULL, m);
+ }
+ free(bcast_entries);
+
+ return num_entries;
+}
+
+
static void
api_epg_episode_broadcasts
( access_t *perm, htsmsg_t *l, const char *lang, epg_broadcast_t *ep,
uint32_t *entries, epg_broadcast_t *ebc_skip )
{
- epg_broadcast_t *ebc;
- htsmsg_t *m;
epg_set_t *episodelink = ep->episodelink;
- epg_set_item_t *item;
if (episodelink == NULL)
return;
- LIST_FOREACH(item, &episodelink->broadcasts, item_link) {
- ebc = item->broadcast;
- if (ebc != ebc_skip) {
- m = api_epg_entry(ebc, lang, perm, NULL);
- htsmsg_add_msg(l, NULL, m);
- (*entries)++;
- }
- }
+ /* Need to sort these ourselves since they are used as a livegrid
+ * which requires remote sort.
+ */
+ *entries = api_epg_episode_sorted(episodelink, perm, l, lang, ebc_skip);
}
static int
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
{
uint32_t id, entries = 0;
- htsmsg_t *l = htsmsg_create_list(), *m;
- epg_broadcast_t *e, *ebc;
+ htsmsg_t *l = htsmsg_create_list();
+ epg_broadcast_t *e;
char *lang;
epg_set_t *serieslink;
- epg_set_item_t *item;
if (htsmsg_get_u32(args, "eventId", &id))
return -EINVAL;
pthread_mutex_lock(&global_lock);
e = epg_broadcast_find_by_id(id);
serieslink = e->serieslink;
- if (serieslink) {
- LIST_FOREACH(item, &serieslink->broadcasts, item_link) {
- ebc = item->broadcast;
- if (ebc != e) {
- m = api_epg_entry(ebc, lang, perm, NULL);
- htsmsg_add_msg(l, NULL, m);
- entries++;
- }
- }
- }
+ if (serieslink)
+ entries = api_epg_episode_sorted(serieslink, perm, l, lang, e);
+
pthread_mutex_unlock(&global_lock);
free(lang);
.desc = N_("Broadcast."),
.set = dvr_entry_class_broadcast_set,
.get = dvr_entry_class_broadcast_get,
- .opts = PO_RDONLY | PO_NOUI,
+ /* Has to be available to UI for "show duplicate" from dvr upcoming */
+ .opts = PO_RDONLY | PO_HIDDEN,
},
{
.type = PT_STR,
return content
}
- function getDialogButtons(title) {
+ function getDialogButtons(d) {
+ var title = getTitle(d);
var buttons = [];
+ var eventId = d[0].params[24].value;
var comboGetInfo = new Ext.form.ComboBox({
store: new Ext.data.ArrayStore({
if (title)
buttons.push(comboGetInfo);
+ buttons.push(new Ext.Toolbar.Button({
+ handler: function() { epgAlternativeShowingsDialog(eventId, true) },
+ iconCls: 'duprec',
+ tooltip: _('Find alternative showings for the DVR entry.'),
+ }));
+ buttons.push(new Ext.Toolbar.Button({
+ handler: function() { epgAlternativeShowingsDialog(eventId, false) },
+ iconCls: 'epgrelated',
+ tooltip: _('Find related showings for the DVR entry.'),
+ }));
+
buttons.push(new Ext.Button({
id: previousButtonId,
handler: previousEvent,
function showit(d) {
var dialogTitle = getDialogTitle(d);
var content = getDialogContent(d);
- var buttons = getDialogButtons(getTitle(d));
+ var buttons = getDialogButtons(d);
var windowHeight = Ext.getBody().getViewSize().height - 150;
+
win = new Ext.Window({
title: dialogTitle,
iconCls: 'info',
list: 'channel_icon,disp_title,disp_subtitle,disp_summary,episode_disp,start_real,stop_real,' +
'duration,disp_description,status,filesize,comment,duplicate,' +
'autorec_caption,timerec_caption,image,copyright_year,credits,keyword,category,' +
- 'first_aired,genre,channelname,fanart_image',
+ 'first_aired,genre,channelname,fanart_image,broadcast',
+ },
+ success: function(d) {
+ d = json_decode(d);
+ tvheadend.loading(0),
+ cb(d);
+ },
+ failure: function(d) {
+ tvheadend.loading(0);
+ }
+ });
+ } // load
+
+ function previousEvent(b, e) {
+ --current_index;
+ load(store,current_index,updateit);
+ }
+ function nextEvent(b, e) {
+ ++current_index;
+ var cbWin = b.findParentByType(Ext.Window);
+ load(store,current_index,updateit);
+ }
+ function dvrAlternativeShowings(eventId) {
+ var store = getAlternativeShowingsStore(eventId);
+
+ var detailsfcn = function(grid, rec, act, row) {
+ var store = grid.getStore();
+ var event = store.getAt(row);
+ var data = event.data;
+ new tvheadend.epgDetails(grid, row);
+ };
+
+ var eventdetails = new Ext.ux.grid.RowActions({
+ id: 'details',
+ header: _('Details'),
+ tooltip: _('Details'),
+ width: 67,
+ dataIndex: 'actions',
+ callbacks: {
+ 'recording': detailsfcn,
+ 'recordingError': detailsfcn,
+ 'scheduled': detailsfcn,
+ 'completed': detailsfcn,
+ 'completedError': detailsfcn
+ },
+ actions: [
+ {
+ iconCls: 'broadcast_details',
+ qtip: _('Broadcast details'),
+ cb: detailsfcn
+ },
+ {
+ iconIndex: 'dvrState'
+ }
+ ]
+ });
+
+ var epgView = new Ext.ux.grid.livegrid.GridView({
+ nearLimit: 100,
+ loadMask: {
+ msg: _('Buffering. Please wait…')
+ },
+ });
+
+ var grid = new Ext.ux.grid.livegrid.GridPanel({
+ store: store,
+ plugins: [eventdetails],
+ iconCls: 'epg',
+ view: epgView,
+ cm: new Ext.grid.ColumnModel({
+ columns: [
+ eventdetails,
+ {
+ width: 250,
+ id: 'title',
+ header: _("Title"),
+ tooltip: _("Title"),
+ dataIndex: 'title',
+ },
+ {
+ width: 250,
+ id: 'extratext',
+ header: _("Extra text"),
+ tooltip: _("Extra text: subtitle or summary or description"),
+ dataIndex: 'extratext',
+ renderer: dvrRenderExtraText
+ },
+ {
+ width: 200,
+ id: 'start',
+ header: _("Start Time"),
+ tooltip: _("Start Time"),
+ dataIndex: 'start',
+ renderer: dvrRenderDate
+ },
+ {
+ width: 200,
+ id: 'stop',
+ header: _("End Time"),
+ tooltip: _("End Time"),
+ dataIndex: 'stop',
+ renderer: dvrRenderDate
+ },
+ {
+ width: 250,
+ id: 'channelName',
+ header: _("Channel"),
+ tooltip: _("Channel"),
+ dataIndex: 'channelName',
+ },
+ ],
+ }),
+ }); // grid
+
+
+ var windowHeight = Ext.getBody().getViewSize().height - 150;
+
+ win = new Ext.Window({
+ title: 'Alternative Showings',
+ iconCls: 'info',
+ layout: 'fit',
+ width: 1200,
+ height: windowHeight,
+ constrainHeader: true,
+ buttonAlign: 'center',
+ autoScroll: true,
+ items: grid,
+ });
+ // Handle comet updates until user closes dialog.
+ var update = function(m) {
+ tvheadend.epgCometUpdate(m, store);
+ };
+ tvheadend.comet.on('epg', update);
+ win.on('close', function(panel, opts) {
+ tvheadend.comet.un('epg', update);
+ });
+
+ win.show();
+ updateDialogFanart(d);
+ checkButtonAvailability(win.fbar)
+ }
+
+ function load(store, index, cb) {
+ var uuid = store.getAt(index).id;
+ tvheadend.loading(1);
+ Ext.Ajax.request({
+ url: 'api/idnode/load',
+ params: {
+ uuid: uuid,
+ list: 'channel_icon,disp_title,disp_subtitle,disp_summary,episode_disp,start_real,stop_real,' +
+ 'duration,disp_description,status,filesize,comment,duplicate,' +
+ 'autorec_caption,timerec_caption,image,copyright_year,credits,keyword,category,' +
+ 'first_aired,genre,channelname,fanart_image,broadcast',
},
success: function(d) {
d = json_decode(d);
function updateit(d) {
var dialogTitle = getDialogTitle(d);
var content = getDialogContent(d);
- var buttons = getDialogButtons(getTitle(d));
+ var buttons = getDialogButtons(d);
win.removeAll();
// Can't update buttons at the same time...
win.update({html: content});
}
};
- function updateDupText(button, dup) {
- button.setText(dup ? _('Hide duplicates') : _('Show duplicates'));
- }
-
- var dupButton = {
- name: 'dup',
- builder: function() {
- return new Ext.Toolbar.Button({
- tooltip: _('Toggle the view of the duplicate DVR entries.'),
- iconCls: 'duprec',
- text: _('Show duplicates')
- });
- },
- callback: function(conf, e, store, select) {
- duplicates ^= 1;
- select.grid.colModel.setHidden(columnId, !duplicates);
- select.grid.bottomToolbar.changePage(0);
- updateDupText(this, duplicates);
- store.baseParams.duplicates = duplicates;
- store.reload();
- }
- };
-
function selected(s, abuttons) {
var recording = 0;
s.each(function(s) {
abuttons.stop.setDisabled(recording < 1);
abuttons.abort.setDisabled(recording < 1);
abuttons.prevrec.setDisabled(recording >= 1);
+ var r = s.getSelections();
+ abuttons.epgalt.setDisabled(r.length <= 0);
+ abuttons.epgrelated.setDisabled(r.length <= 0);
}
function beforeedit(e, grid) {
return false;
}
- function viewready(grid) {
- var d = grid.store.baseParams.duplicates;
- updateDupText(grid.abuttons['dup'], d);
- if (!d) {
- columnId = grid.colModel.findColumnIndex('duplicate');
- grid.colModel.setHidden(columnId, true);
- }
- }
-
tvheadend.idnode_grid(panel, {
url: 'api/dvr/entry',
gridURL: 'api/dvr/entry/grid_upcoming',
del: true,
list: 'category,enabled,duplicate,disp_title,disp_extratext,episode_disp,' +
'channel,image,copyright_year,start_real,stop_real,duration,pri,filesize,' +
- 'sched_status,errors,data_errors,config_name,owner,creator,comment,genre',
+ 'sched_status,errors,data_errors,config_name,owner,creator,comment,genre,broadcast',
columns: {
disp_title: {
renderer: tvheadend.displayWithYearAndDuplicateRenderer(),
actions,
tvheadend.contentTypeAction,
],
- tbar: [stopButton, abortButton, prevrecButton, dupButton],
+ tbar: [stopButton, abortButton, prevrecButton, epgShowRelatedButtonConf, epgShowAlternativesButtonConf],
selected: selected,
beforeedit: beforeedit,
- viewready: viewready
});
return panel;
scope.insert(0,new placeholder({val: _('(Clear filter)'), key: '-1'}));
};
+epgDetailsfcn = function(grid, rec, act, row) {
+ new tvheadend.epgDetails(grid, row);
+};
+
+// Each column model needs their own copy
+// since it gets explicitly detstroyed.
+var getEPGEventDetails = function() {
+ var ra = new Ext.ux.grid.RowActions({
+ id: 'details',
+ header: _('Details'),
+ tooltip: _('Details'),
+ width: 67,
+ dataIndex: 'actions',
+ callbacks: {
+ 'recording': epgDetailsfcn,
+ 'recordingError': epgDetailsfcn,
+ 'scheduled': epgDetailsfcn,
+ 'completed': epgDetailsfcn,
+ 'completedError': epgDetailsfcn
+ },
+ actions: [
+ {
+ iconCls: 'broadcast_details',
+ qtip: _('Broadcast details'),
+ cb: epgDetailsfcn
+ },
+ {
+ iconIndex: 'dvrState'
+ }
+ ]
+ });
+ // RowActions do not have a destroy function which means when the
+ // ColumnModel is destroyed (such as in a dialog box), then we get
+ // an exception. So define our own null destroy function.
+ ra.destroy=function() {}
+ return ra;
+}
+
+tvheadend.epgCometUpdate = function(m, epgStore) {
+ if ('delete' in m)
+ Ext.each(m['delete'], function(d) {
+ var r = epgStore.getById(d);
+ if (r)
+ epgStore.remove(r);
+ });
+ if (m.update || m.dvr_update || m.dvr_delete) {
+ var a = m.update || m.dvr_update || m.dvr_delete;
+ if (m.update && m.dvr_update)
+ var a = m.update.concat(m.dvr_update);
+ if (m.update || m.dvr_update)
+ a = a.concat(m.dvr_delete);
+ var ids = [];
+ Ext.each(a, function (id) {
+ var r = epgStore.getById(id);
+ if (r)
+ ids.push(r.id);
+ });
+ if (ids) {
+ Ext.Ajax.request({
+ url: 'api/epg/events/load',
+ params: {
+ eventId: Ext.encode(ids)
+ },
+ success: function(d) {
+ d = json_decode(d);
+ Ext.each(d, function(jd) {
+ tvheadend.replace_entry(epgStore.getById(jd.eventId), jd);
+ });
+ },
+ failure: function(response, options) {
+ Ext.MessageBox.alert(_('EPG Update'), response.statusText);
+ }
+ });
+ }
+ }
+};
+
+
tvheadend.ContentGroupStore = tvheadend.idnode_get_enum({
url: 'api/epg/content_type/list',
listeners: {
tooltip: _('Create an automatic recording rule to record all future programs that match the current query.'),
text: event.serieslinkUri ? _("Record series") : _("Autorec")
}));
+
+ var eventId = event.eventId;
+ buttons.push(new Ext.Toolbar.Button({
+ handler: function() { epgAlternativeShowingsDialog(eventId, true) },
+ iconCls: 'duprec',
+ tooltip: _('Find alternative showings for the DVR entry.'),
+ }));
+ buttons.push(new Ext.Toolbar.Button({
+ handler: function() { epgAlternativeShowingsDialog(eventId, false) },
+ iconCls: 'epgrelated',
+ tooltip: _('Find related showings for the DVR entry.'),
+ }));
+
buttons.push(new Ext.Button({
id: previousButtonId,
handler: previousEvent,
} //getDialogButtons
var current_index = index;
- var event = grid.getStore().getAt(index).data;
var store = grid.getStore();
+ var event = store.getAt(index).data;
var content = getDialogContent(event);
var buttons = getDialogButtons();
var windowHeight = Ext.getBody().getViewSize().height - 150;
var lookup = '<span class="x-linked"> </span>';
var epgChannelCurrentIndex = 0;
- var detailsfcn = function(grid, rec, act, row) {
- new tvheadend.epgDetails(grid, row);
- };
var watchfcn = function(grid, rec, act, row) {
var item = grid.getStore().getAt(row);
new tvheadend.VideoPlayer(item.data.channelUuid);
};
- var eventdetails = new Ext.ux.grid.RowActions({
- id: 'details',
- header: _('Details'),
- tooltip: _('Details'),
- width: 67,
- dataIndex: 'actions',
- callbacks: {
- 'recording': detailsfcn,
- 'recordingError': detailsfcn,
- 'scheduled': detailsfcn,
- 'completed': detailsfcn,
- 'completedError': detailsfcn
- },
- actions: [
- {
- iconCls: 'broadcast_details',
- qtip: _('Broadcast details'),
- cb: detailsfcn
- },
- {
- iconIndex: 'dvrState'
- }
- ]
- });
-
+ var eventdetails = getEPGEventDetails();
var eventactions = new Ext.ux.grid.RowActions({
id: 'eventactions',
header: _('Actions'),
tvheadend.comet.on('epg', function(m) {
if (!panel.isVisible())
return;
- if ('delete' in m)
- Ext.each(m['delete'], function(d) {
- var r = epgStore.getById(d);
- if (r)
- epgStore.remove(r);
- });
- if (m.update || m.dvr_update || m.dvr_delete) {
- var a = m.update || m.dvr_update || m.dvr_delete;
- if (m.update && m.dvr_update)
- var a = m.update.concat(m.dvr_update);
- if (m.update || m.dvr_update)
- a = a.concat(m.dvr_delete);
- var ids = [];
- Ext.each(a, function (id) {
- var r = epgStore.getById(id);
- if (r)
- ids.push(r.id);
- });
- if (ids) {
- Ext.Ajax.request({
- url: 'api/epg/events/load',
- params: {
- eventId: Ext.encode(ids)
- },
- success: function(d) {
- d = json_decode(d);
- Ext.each(d, function(jd) {
- tvheadend.replace_entry(epgStore.getById(jd.eventId), jd);
- });
- },
- failure: function(response, options) {
- Ext.MessageBox.alert(_('EPG Update'), response.statusText);
- }
- });
- }
- }
+ tvheadend.epgCometUpdate(m, epgStore);
});
// Always reload the store when the tab is activated
--- /dev/null
+/*
+ * epgevent.js
+ * EPG dialogs for broadcast events.
+ * Copyright (C) 2018 Tvheadend Foundation CIC
+ */
+
+/// Display dialog showing alternative showings for a broadcast event.
+/// @param alternative - If true then display "alternatives", otherwise display "related" broadcasts
+function epgAlternativeShowingsDialog(eventId, alternative) {
+ // Default params only exist in ECMA2015+, so do it old way.
+ alternative = (typeof alternative !== 'undefined') ? alternative : true;
+
+ function getAlternativeShowingsStore(eventId) {
+ var base = alternative ? "alternative" : "related";
+ return new Ext.ux.grid.livegrid.Store({
+ autoLoad: true,
+ // Passing params doesn't seem to work, so force eventId in to url.
+ url: 'api/epg/events/' + base + '?eventId='+eventId,
+ baseParams: {
+ eventId: eventId,
+ },
+ bufferSize: 300,
+ selModel: new Ext.ux.grid.livegrid.RowSelectionModel(),
+ reader: new Ext.ux.grid.livegrid.JsonReader({
+ root: 'entries',
+ totalProperty: 'totalCount',
+ id: 'eventId'
+ }, [
+ // We need a complete set of fields since user can request
+ // dialog that retrieves its data from our store.
+ { name: 'eventId' },
+ { name: 'channelName' },
+ { name: 'channelUuid' },
+ { name: 'channelNumber' },
+ { name: 'channelIcon' },
+ { name: 'title' },
+ { name: 'subtitle' },
+ { name: 'summary' },
+ { name: 'description' },
+ { name: 'extratext' },
+ { name: 'episodeOnscreen' },
+ { name: 'image' },
+ {
+ name: 'start',
+ type: 'date',
+ dateFormat: 'U' /* unix time */
+ },
+ {
+ name: 'stop',
+ type: 'date',
+ dateFormat: 'U' /* unix time */
+ },
+ {
+ name: 'first_aired',
+ type: 'date',
+ dateFormat: 'U' /* unix time */
+ },
+ { name: 'duration' },
+ { name: 'starRating' },
+ { name: 'credits' },
+ { name: 'category' },
+ { name: 'keyword' },
+ { name: 'ageRating' },
+ { name: 'copyright_year' },
+ { name: 'new' },
+ { name: 'genre' },
+ { name: 'dvrUuid' },
+ { name: 'dvrState' },
+ { name: 'serieslinkUri' }
+ ])
+ });
+ } //getAlternativeShowingsStore
+
+
+ var store = getAlternativeShowingsStore(eventId);
+ var epgView = new Ext.ux.grid.livegrid.GridView({
+ nearLimit: 100,
+ loadMask: {
+ msg: _('Buffering. Please wait…')
+ },
+ });
+
+ var epgEventDetails = getEPGEventDetails();
+
+ var grid = new Ext.ux.grid.livegrid.GridPanel({
+ store: store,
+ plugins: [epgEventDetails],
+ iconCls: 'epg',
+ view: epgView,
+ cm: new Ext.grid.ColumnModel({
+ columns: [
+ epgEventDetails,
+ {
+ width: 250,
+ id: 'title',
+ header: _("Title"),
+ tooltip: _("Title"),
+ dataIndex: 'title',
+ },
+ {
+ width: 250,
+ id: 'extratext',
+ header: _("Extra text"),
+ tooltip: _("Extra text: subtitle or summary or description"),
+ dataIndex: 'extratext',
+ renderer: tvheadend.renderExtraText
+ },
+ {
+ width: 100,
+ id: 'episodeOnscreen',
+ header: _("Episode"),
+ tooltip: _("Episode"),
+ dataIndex: 'episodeOnscreen',
+ },
+ {
+ width: 200,
+ id: 'start',
+ header: _("Start Time"),
+ tooltip: _("Start Time"),
+ dataIndex: 'start',
+ renderer: tvheadend.renderCustomDate
+ },
+ {
+ width: 200,
+ id: 'stop',
+ header: _("End Time"),
+ tooltip: _("End Time"),
+ dataIndex: 'stop',
+ renderer: tvheadend.renderCustomDate
+ },
+ {
+ width: 250,
+ id: 'channelName',
+ header: _("Channel"),
+ tooltip: _("Channel"),
+ dataIndex: 'channelName',
+ },
+ ],
+ }),
+ }); // grid
+
+
+ var windowHeight = Ext.getBody().getViewSize().height - 150;
+
+ var win = new Ext.Window({
+ title: alternative ? _('Alternative Showings') : _('Related Showings'),
+ iconCls: 'info',
+ layout: 'fit',
+ width: 1317,
+ height: windowHeight,
+ constrainHeader: true,
+ buttonAlign: 'center',
+ autoScroll: false, // Internal grid has its own scrollbars so no need for us to have them
+ items: grid,
+ bbar: new Ext.ux.grid.livegrid.Toolbar(
+ tvheadend.PagingToolbarConf({view: epgView},_('Events'),0,0)
+ ),
+
+ });
+
+ // Handle comet updates until user closes dialog.
+ var update = function(m) {
+ tvheadend.epgCometUpdate(m, store);
+ };
+ tvheadend.comet.on('epg', update);
+ win.on('close', function(panel, opts) {
+ tvheadend.comet.un('epg', update);
+ });
+
+ win.show();
+}
+
+
+var epgAlternativeShowingsDialogForSelection = function(conf, e, store, select, alternative) {
+ var r = select.getSelections();
+ if (r && r.length > 0) {
+ for (var i = 0; i < r.length; i++) {
+ var rec = r[i];
+ var eventId = rec.data['broadcast'];
+ if (eventId)
+ epgAlternativeShowingsDialog(eventId, alternative);
+ }
+ }
+};
+
+var epgShowRelatedButtonConf = {
+ name: 'epgrelated',
+ builder: function() {
+ return new Ext.Toolbar.Button({
+ tooltip: _('Display dialog of related broadcasts'),
+ iconCls: 'epgrelated',
+ text: _('Related broadcasts'),
+ disabled: true
+ });
+ },
+ callback: function(conf, e, store, select) {
+ epgAlternativeShowingsDialogForSelection(conf, e, store, select, false);
+ }
+}
+
+var epgShowAlternativesButtonConf = {
+ name: 'epgalt',
+ builder: function() {
+ return new Ext.Toolbar.Button({
+ tooltip: _('Display dialog showing alternative broadcasts'),
+ iconCls: 'duprec',
+ text: _('Alternative showings'),
+ disabled: true
+ });
+ },
+ callback: function(conf, e, store, select) {
+ epgAlternativeShowingsDialogForSelection(conf, e, store, select, true);
+ }
+};
background-image: url(../icons/accept.png) !important;
}
+.epgrelated {
+ background-image: url(../icons/clock.png) !important;
+}
+
.duprec {
background-image: url(../icons/control_repeat_blue.png) !important;
}
tvheadend.applyHighResIconPath(tvheadend.uniqueArray(ret_minor)).join("") + '</span>';
}
+tvheadend.renderCustomDate = function(value, meta, record) {
+ if (value) {
+ var dt = new Date(value);
+ return tvheadend.toCustomDate(dt,tvheadend.date_mask);
+ }
+ return "";
+}
+
+tvheadend.renderExtraText = function(value, meta, record) {
+ value = record.data.subtitle;
+ if (!value) {
+ value = record.data.summary;
+ if (!value)
+ value = record.data.description;
+ }
+ return value;
+}
+
tvheadend.displayCategoryIcon = function(value, meta, record, ri, ci, store) {
if (value == null)
return '';