diff --git a/doc/application-x-subrip.png b/doc/application-x-subrip.png
new file mode 100644
index 0000000..d592247
Binary files /dev/null and b/doc/application-x-subrip.png differ
diff --git a/doc/audio-volume-high.png b/doc/audio-volume-high.png
new file mode 100644
index 0000000..917364f
Binary files /dev/null and b/doc/audio-volume-high.png differ
diff --git a/doc/audio-volume-low.png b/doc/audio-volume-low.png
new file mode 100644
index 0000000..7f8986d
Binary files /dev/null and b/doc/audio-volume-low.png differ
diff --git a/doc/audio-volume-medium.png b/doc/audio-volume-medium.png
new file mode 100644
index 0000000..34cb503
Binary files /dev/null and b/doc/audio-volume-medium.png differ
diff --git a/doc/audio-volume-muted.png b/doc/audio-volume-muted.png
new file mode 100644
index 0000000..ac2e9c8
Binary files /dev/null and b/doc/audio-volume-muted.png differ
diff --git a/doc/configure-shortcuts.png b/doc/configure-shortcuts.png
new file mode 100644
index 0000000..6501836
Binary files /dev/null and b/doc/configure-shortcuts.png differ
diff --git a/doc/configure.png b/doc/configure.png
new file mode 100644
index 0000000..fedf74d
Binary files /dev/null and b/doc/configure.png differ
diff --git a/doc/dialog-information.png b/doc/dialog-information.png
new file mode 100644
index 0000000..cfcb72e
Binary files /dev/null and b/doc/dialog-information.png differ
diff --git a/doc/document-open-folder.png b/doc/document-open-folder.png
new file mode 100644
index 0000000..81716c1
Binary files /dev/null and b/doc/document-open-folder.png differ
diff --git a/doc/document-open-recent.png b/doc/document-open-recent.png
new file mode 100644
index 0000000..4209fa8
Binary files /dev/null and b/doc/document-open-recent.png differ
diff --git a/doc/document-save-as.png b/doc/document-save-as.png
new file mode 100644
index 0000000..5688e25
Binary files /dev/null and b/doc/document-save-as.png differ
diff --git a/doc/document-save.png b/doc/document-save.png
index 5e813d1..b97b6a9 100644
Binary files a/doc/document-save.png and b/doc/document-save.png differ
diff --git a/doc/edit-clear-list.png b/doc/edit-clear-list.png
new file mode 100644
index 0000000..4e3fc49
Binary files /dev/null and b/doc/edit-clear-list.png differ
diff --git a/doc/edit-delete.png b/doc/edit-delete.png
new file mode 100644
index 0000000..2d77a2a
Binary files /dev/null and b/doc/edit-delete.png differ
diff --git a/doc/edit-rename.png b/doc/edit-rename.png
new file mode 100644
index 0000000..924224e
Binary files /dev/null and b/doc/edit-rename.png differ
diff --git a/doc/format-justify-center.png b/doc/format-justify-center.png
new file mode 100644
index 0000000..c9eb729
Binary files /dev/null and b/doc/format-justify-center.png differ
diff --git a/doc/go-jump.png b/doc/go-jump.png
new file mode 100644
index 0000000..9f9ec26
Binary files /dev/null and b/doc/go-jump.png differ
diff --git a/doc/gtk-quit.png b/doc/gtk-quit.png
new file mode 100644
index 0000000..abab448
Binary files /dev/null and b/doc/gtk-quit.png differ
diff --git a/doc/index.docbook b/doc/index.docbook
index ab94346..579aa4a 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -1,729 +1,1397 @@
Kaffeine">
]>
The &kaffeine; Handbook
Jürgen
Kofler
kaffeine@gmx.net
Christophe
Thommeret
hftom@free.fr
Mauro
Carvalho
Chehab
mchehab+kde@kernel.org
2004
2016
2017
The &kaffeine; Authors
&FDLNotice;
-2017-11-11
+2017-11-26
&kaffeine; 2.0.14
Kaffeine is a Media Player by &kde;.
kaffeine
vlc
video
audio
mp3
dvd
atsc
dvb-t
dvb-c
dvb-s
dvb-t2
dvb-s2
isdb-t
tv
&kaffeine; Player
The Start Window
Like many other media players, &kaffeine; supports a wide range of video and audio formats
as well as playing audio and video from DVD and &CD;. Additionally, it supports live
Digital TV playback if your machine has a Digital Video Broadcast (DVB)
device plugged into it.
Once &kaffeine; starts, it presents a screen with the main functions in the middle,
and it’s possible to switch to other functions via either the menu bar or the left sidebar:
Start Window
Start Window
Play a File
Playing a video or audio file is as simple as clicking on the Play File
button. It will ask you to select the file to be played, and will begin playing it. Since &kaffeine;
uses LibVLC for the backend, it supports all the same file formats as VLC.
It also supports opening a list of files to create and manage playlists.
Play a File
Play a File
The Playlist Window
&kaffeine; supports multiple playlists. Choose the active one with the Playlist selector in
the left sidebar. You can easily drag some files or folders from the file browser and drop it on the playlist to enqueue
or drop it on the player window to create a new playlist. To change a playlist name edit it and confirm with Return.
Playlist Window
Playlist Window
You can find all playlist related functions in the Playlist item from the menu bar
and the context-menu (right-click on playlist).
Digital TV Player
Digital TV configuration
While the basic functionality is useful enough for someone who wants a simple, yet powerful
media player, the best feature in &kaffeine; is to use it as a &GUI; frontend to watch and record digital TV.
Since &kaffeine; version 2.0, the digital TV support uses libdvbv5 and
was extended to support new standards like DVB-T2 and ISDB-T. Extending its
support for newer digital TV standards is now simpler.
Also, since it uses the &Linux; DVB version 5 API, it supports frontends
capable of implementing multiple digital TV standards.
Setting the TV configuration on &kaffeine; is as simple as open the
Television item from the menu bar and select the
Configure Television... option. A pop up window
will open, allowing setting the parameters to be used:
TV Configuration - General Options
TV Configuration - General Options
General digital TV settings
The General Options menu allows setting the
device-independent settings.
The Recording folder sets the location where all TV
program records will be stored.
The Time shift folder is used in conjunction with the
pause button
() of the media player screen. When the button
is pressed, a time shift file will be stored at the location pointed by this
menu option.
The Begin margin and End margin
options are used to setup a sort of security margin
in order
to avoid losing the beginning and the end of a program, as the time stamps at
the program guide may not be precise. So, it actually starts recording a few
minutes before the Start time defined in the
Program Guide. The exact amount of time before is
defined via Begin margin. &kaffeine; extends the
record by the amount of time defined by End margin
after the end of the program.
The Naming style for recordings option is used to setup
how &kaffeine; will name a program. Several macros can be used to dynamically
change the name of the record:
%title - Title of the program, as seen at the
Program Guide and Recording Schedule
menus;
%day, %month, %year, %hour, %min, %sec - Fields from the
time stamp with represents the time when &kaffeine; starts recording a program;
%channel - Name of the channel that streamed the program.
The Action after recording finishes option is used to
setup an optional command to be executed when &kaffeine; stops recording a
program.
Clicking at the Update Scan Data over Internet option
makes &kaffeine; to check if a new channel scanning definition file is
present at
KDE's site.
&kaffeine; uses a file called scanfile.dvb to store a list of known
digital TV channels per Country and City. This file is kept in sync with
the contents of the
dtv-scan-tables tree, maintained
by LinuxTV community. For more details,
please see the
dtv-scan-tables wiki page.
When the button is clicked, &kaffeine; will download the latest version of the
channel definitions and store on a user-specific local data file, overriding
any contents of a previous one.
Please notice that, in order to use the newest definitions, it is
required to close the TV configuration dialog and reopen.
The Edit scanfile option allows
editing the file, which can be useful to add a new set of channels, while
the upstream file is not updated. If you need to use it, please consider
sending an update to
dtv-scan-tables
for others to also benefit from the new channel definitions.
The Use ISO 8859-1 charset instead of ISO 6937
option allows selecting the default to be used on MPEG-TS messages
that don't explicitly set a charset. If not set, it defaults to using
ISO-6937 encoding. If set, the default changes to ISO 8859-1.
The Create info files to accompany EPG recordings
option enables the creation of ancillary files for scheduled records
with the contents of the program guide for records made via the
Program guide.
Configuring digital TV sources
TV Device Configuration
TV Configuration
The Device tabs have the per-device setup. Usually
selecting the country and the city in the Source
combo box (or using one of the Autoscan sources) is enough for non-satellite
configurations.
The same happens for devices that support multiple TV standards. On those
devices, you need to setup the Source for each
TV standard:
Channel Configuration
Channel Configuration
The Tuner timeout specifies the amount of time the
channel scan will wait to get a signal lock. Usually, the default is
enough for most devices, but if the device is too slow to lock, such
value can be increased.
The Name specifies the name associated with source
that will appear selecting Television item from the menubar
and Channels option, for settings panel.
Satellite devices
The configuration for satellite devices (DVB-S, DVB-S2) are more complex,
as there are different satellite system arrangements that are possible.
Also, on satellite systems, it is usually up to the device to power up an
amplifier located at the satellite dish - called LNBf - via a DC voltage.
Also, as satellite systems use a wide bandwidth and accept signals using
different polarities, it is common to use a protocol - called DiSEqC - in
order to select a range of channels to be received.
The first step is to teach &kaffeine; about the satellite configuration
via the Configuration combo box:
Satellite device definitions
Satellite device definitions
The following values are possible:
DiSEqC Switch - The antenna cable is connected to
a DiSEqC switch or the LNBf requires DiSEqC commands to select a range
of channels. This is the most common setting. It allows having up to 4
satellites connected at the same time, each with its own dish. For each
satellite, you need to set the configuration for the LNBf at the dish
pointing to it.
USALS Rotor - The antenna cable is connected to
a single dish with a USALS Rotor, controlled via DiSEqC. Multiple satellites
can be configured, as the rotor will change the dish position when a different
satellite is chosen. You need to specify the position of the dish
(latitude, longitude).
Positions Rotor - The antenna cable is connected to
a single dish with a Rotor with a set of fixed positions, controlled via
DiSEqC. Multiple satellites can be configured, as the rotor will change the
dish position when a different satellite is chosen.
Disable DiSEqC - The antenna cable is connected
to a dish without any elements supporting DiSEqC. This setup is typically
used with multipoint bandstacked LNBf, where all channels are present at
the same time at the antenna cabling.
On a typical satellite system, the LNBf uses the power up voltage to select
between a lower voltage (13V) for vertical or circular right polarization and
a higher voltage (18V) for horizontal or circular left polarization.
However, due to cabling loss, sometimes the LNBf doesn't understand the
high voltage and several channels won't tune or will tune wrong. So, a few
devices offer an option to increase the voltage to a higher setting
(14V or 19V). This is enabled via the tri-state
Use Higher LNBf voltage option. Three values
are possible:
tri-state - Don't send a command to the device to
adjust the voltage level. That's the default.
unselected - Use normal values (13V/18V) for the
DC voltage. Only select it if the device supports adjusting the level.
selected - Use higher values (14V/19V) for the
DC voltage. Only select it if the device supports adjusting the level.
Configurations without a rotor
When either DiSEqC Switch or Disable DiSEqC
options are used, the first step is to set the satellite that
will be used as a signal source, via a combo box on the right. With a
DiSEqC switch, it is possible to select up to 4 sources.
Each with its own LNBf. After setting the source(s), for each source, click at
the corresponding LNBf Settings button to open a popup
window to select the LNBf type inside the dish that corresponds to the source:
LNBf definitions
LNBf definitions
Rotor configurations
When a rotor is used, there is just one LNBf which is shared with multiple
satellites. So, the next step is to select the LNBf type via the
LNB Settings dialog.
For USALS rotor, the positioning is done via satellite position (latitude,
longitude). So, just select the satellites that will be used via a combo box
and click at the Add Satellite button.
For positions rotor, the positioning is done via a preconfigured position
number. So, just select the satellite position at the number dialog on the
left and the satellite via a combo box on the right and click at the
Add Satellite button.
If a satellite was added by mistake, you can select the satellite and
click at the Remove Satellite button to remove it.
Digital TV channel setup
After clicking on the Ok button, the next step is to
scan for the digital channels, using the Television item
from the menubar and select Channels option, for
settings panel:
Scanning Channels
Scanning Channels
If more then one standard is supported, the Source
combo box will allow you to select the one that will be used to scan. Don't
forget to connect the device's antenna cable to match the standard that
will be used.
When &kaffeine; identifies a channel, it reads a MPEG-TS
table called Network Information Table (NIT), which contains
information about channels using different tuning parameters transmitted by
the same broadcaster. On certain networks, it is possible that some tuning
parameters to be stored on several NIT tables
(called other NITs
). This is more common on some cable and
satellite systems. By selecting Search transponders for other
Networks, &kaffeine; is instructed to wait and parse all other
NITs, which may make it to find more channels, at the cost
of taking a lot more time to complete the channel scan operation.
The channel scan operation is started by clicking on
Start Scan.
Once finished, the discovered channels will appear on the right. These channels can be copied
to the left side by clicking Add Filtered. It is possible to check the tuning parameters
for the channel in the left side by clicking on the Edit button.
Some parameters are adjustable in the window that pops up.
Edit Channel Settings
Edit Channel Settings
Once the channels are saved, watching TV is as simple as clicking on the Digital TV button in the main window:
Watching TV
Watching TV
Watching TV
&kaffeine; also allows you to click on the
button to pause it.
With this action, &kaffeine; will record the program and once the
button is pressed it will start the program from
the point it was paused, this is known as time shifting. There is also a
button
that allows you to quick record and save the program to disk.
Program Guide
Digital TV channels usually transmit a list of the current and future
attractions. This is called Electronic Program Guide - EPG.
The EPG data is captured when a channel's content is played.
To see the EPG, open the Television item
from the menubar and select the Program Guide option:
Program Guide
Program Guide
On some Countries, the EPG may be available in multiple languages. By
default, &kaffeine; shows any languages on EPG. If multiple languages
are available for a given EPG entry, and no explicit language content is
select, it will prefix the title, subtitle and description data with a 3
letter language code, as defined by ISO 639-2 specification.
The EPG Language option allows filtering just one
language. If enabled, the filter will also be applied to the On
Screen Display - OSD and to any new scheduled
recordings. It won't affect pre-existing scheduled recordings.
Besides clicking on the record button
when the live view is opened, &kaffeine; also allows recording a program
via the program guide, by clicking on the
Record Show at the
Program Guide window.
Recording Schedule
To see the programs that are scheduled to be recorded, open the
Television item from the menubar and select the
Recording Schedule option:
Recording Schedule
Recording Schedule
By clicking one the New button, it is possible
to directly define a time and duration for a program to be recorded.
In this case, it won't use the EPG definitions.
By selecting an existing program and clicking on the
Edit button, you may change the start time
and the record duration. You may also program it to be recorded
weekly or daily.
By selecting an existing program and clicking on the
Remove button, it will remove the program from
the recording schedule.
+
+
+
Copyright and License
Program copyright 2007-2017, The &kaffeine; Authors
Documentation copyright 2003-2005, Jürgen Kofler kaffeine@gmx.net,
Christophe Thommeret hftom@free.fr, Mauro Carvalho Chehab mchehab+kde@kernel.org
&underFDL;
&underGPL;
&documentation.index;
diff --git a/doc/list-add.png b/doc/list-add.png
new file mode 100644
index 0000000..a4e9fa0
Binary files /dev/null and b/doc/list-add.png differ
diff --git a/doc/media-optical-audio.png b/doc/media-optical-audio.png
new file mode 100644
index 0000000..9920031
Binary files /dev/null and b/doc/media-optical-audio.png differ
diff --git a/doc/media-optical-video.png b/doc/media-optical-video.png
new file mode 100644
index 0000000..6a8d1aa
Binary files /dev/null and b/doc/media-optical-video.png differ
diff --git a/doc/media-optical.png b/doc/media-optical.png
new file mode 100644
index 0000000..db93eb4
Binary files /dev/null and b/doc/media-optical.png differ
diff --git a/doc/media-playback-pause.png b/doc/media-playback-pause.png
index 2377717..c233d6a 100644
Binary files a/doc/media-playback-pause.png and b/doc/media-playback-pause.png differ
diff --git a/doc/media-playback-start.png b/doc/media-playback-start.png
index 1113056..cc567d6 100644
Binary files a/doc/media-playback-start.png and b/doc/media-playback-start.png differ
diff --git a/doc/media-playback-stop.png b/doc/media-playback-stop.png
new file mode 100644
index 0000000..859a2ab
Binary files /dev/null and b/doc/media-playback-stop.png differ
diff --git a/doc/media-playlist-repeat.png b/doc/media-playlist-repeat.png
new file mode 100644
index 0000000..2366986
Binary files /dev/null and b/doc/media-playlist-repeat.png differ
diff --git a/doc/media-playlist-shuffle.png b/doc/media-playlist-shuffle.png
new file mode 100644
index 0000000..2ae7675
Binary files /dev/null and b/doc/media-playlist-shuffle.png differ
diff --git a/doc/media-record.png b/doc/media-record.png
index 8bc8573..0f2d3ec 100644
Binary files a/doc/media-record.png and b/doc/media-record.png differ
diff --git a/doc/media-skip-backward.png b/doc/media-skip-backward.png
new file mode 100644
index 0000000..d8ef3ab
Binary files /dev/null and b/doc/media-skip-backward.png differ
diff --git a/doc/media-skip-forward.png b/doc/media-skip-forward.png
new file mode 100644
index 0000000..9519b86
Binary files /dev/null and b/doc/media-skip-forward.png differ
diff --git a/doc/startwindow.png b/doc/startwindow.png
deleted file mode 100644
index dd63ee7..0000000
Binary files a/doc/startwindow.png and /dev/null differ
diff --git a/doc/text-html.png b/doc/text-html.png
new file mode 100644
index 0000000..b09a8ef
Binary files /dev/null and b/doc/text-html.png differ
diff --git a/doc/video-television.png b/doc/video-television.png
new file mode 100644
index 0000000..4679b9f
Binary files /dev/null and b/doc/video-television.png differ
diff --git a/doc/view-fullscreen.png b/doc/view-fullscreen.png
new file mode 100644
index 0000000..83688a1
Binary files /dev/null and b/doc/view-fullscreen.png differ
diff --git a/doc/view-list-details.png b/doc/view-list-details.png
new file mode 100644
index 0000000..5946e94
Binary files /dev/null and b/doc/view-list-details.png differ
diff --git a/doc/view-pim-calendar.png b/doc/view-pim-calendar.png
new file mode 100644
index 0000000..8d8ca47
Binary files /dev/null and b/doc/view-pim-calendar.png differ
diff --git a/doc/view-restore.png b/doc/view-restore.png
new file mode 100644
index 0000000..76de403
Binary files /dev/null and b/doc/view-restore.png differ
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 7e2aa85..40759be 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,865 +1,865 @@
/*
* mainwindow.cpp
*
* Copyright (C) 2007-2011 Christoph Pfister
*
* 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.
*/
#include "log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "configuration.h"
#include "configurationdialog.h"
#include "dbusobjects.h"
#include "dvb/dvbtab.h"
#include "mainwindow.h"
#include "playlist/playlisttab.h"
// log categories. Should match log.h
Q_LOGGING_CATEGORY(logCam, "kaffeine.cam")
Q_LOGGING_CATEGORY(logDev, "kaffeine.dev")
Q_LOGGING_CATEGORY(logDvb, "kaffeine.dvb")
Q_LOGGING_CATEGORY(logDvbSi, "kaffeine.dvbsi")
Q_LOGGING_CATEGORY(logEpg, "kaffeine.epg")
Q_LOGGING_CATEGORY(logConfig, "kaffeine.config")
Q_LOGGING_CATEGORY(logMediaWidget, "kaffeine.mediawidget")
Q_LOGGING_CATEGORY(logPlaylist, "kaffeine.playlist")
Q_LOGGING_CATEGORY(logSql, "kaffeine.sql")
Q_LOGGING_CATEGORY(logVlc, "kaffeine.vlc")
#define FILTER_RULE "kaffeine.*.debug=true"
#define CATEGORIES "cam, dev, dvb, dvbsi, epg, config, mediawidget, playlist, sql, vlc"
class StackedLayout : public QStackedLayout
{
public:
explicit StackedLayout(QWidget *parent) : QStackedLayout(parent) { }
~StackedLayout() { }
QSize minimumSize() const
{
QWidget *widget = currentWidget();
if (widget != NULL) {
return widget->minimumSizeHint();
}
return QSize();
}
};
class StartTab : public TabBase
{
public:
explicit StartTab(MainWindow *mainWindow);
~StartTab() { }
private:
void activate() { }
QAbstractButton *addShortcut(const QString &name, const QIcon &icon, QWidget *parent);
};
StartTab::StartTab(MainWindow *mainWindow)
{
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
QGridLayout *gridLayout = new QGridLayout(this);
gridLayout->setAlignment(Qt::AlignCenter);
gridLayout->setMargin(10);
gridLayout->setSpacing(15);
QAbstractButton *button =
addShortcut(i18n("&1 Play File"), QIcon::fromTheme(QLatin1String("video-x-generic"), QIcon(":video-x-generic")), this);
button->setShortcut(Qt::Key_1);
button->setWhatsThis(i18n("Open dialog to play a file"));
connect(button, SIGNAL(clicked()), mainWindow, SLOT(open()));
gridLayout->addWidget(button, 0, 0);
button = addShortcut(i18n("&2 Play Audio CD"), QIcon::fromTheme(QLatin1String("media-optical-audio"), QIcon(":media-optical-audio")), this);
button->setShortcut(Qt::Key_2);
button->setWhatsThis(i18n("Start playing an audio CD. It assumes that the CD is already there at the CD drive"));
connect(button, SIGNAL(clicked()), mainWindow, SLOT(openAudioCd()));
gridLayout->addWidget(button, 0, 1);
- button = addShortcut(i18n("&3 Play Video CD"), QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical")), this);
+ button = addShortcut(i18n("&3 Play Video CD"), QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical-video")), this);
button->setShortcut(Qt::Key_3);
button->setWhatsThis(i18n("Start playing a Video CD. It assumes that the CD is already there at the CD drive"));
connect(button, SIGNAL(clicked()), mainWindow, SLOT(openVideoCd()));
gridLayout->addWidget(button, 0, 2);
button = addShortcut(i18n("&4 Play DVD"), QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical")), this);
button->setShortcut(Qt::Key_4);
button->setWhatsThis(i18n("Start playing a DVD. It assumes that the DVD is already there at the DVD drive"));
connect(button, SIGNAL(clicked()), mainWindow, SLOT(openDvd()));
gridLayout->addWidget(button, 1, 0);
#if HAVE_DVB == 1
button = addShortcut(i18n("&5 Digital TV"), QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), this);
button->setShortcut(Qt::Key_5);
button->setWhatsThis("Open the Digital TV live view window. If the TV channels are already scanned, it will start playing the last channel");
connect(button, SIGNAL(clicked()), mainWindow, SLOT(playDvb()));
gridLayout->addWidget(button, 1, 1);
#endif /* HAVE_DVB == 1 */
}
QAbstractButton *StartTab::addShortcut(const QString &name, const QIcon &icon, QWidget *parent)
{
// QPushButton has visual problems with big icons
QToolButton *button = new QToolButton(parent);
button->setText(name);
button->setIcon(icon);
button->setFocusPolicy(Qt::NoFocus);
button->setIconSize(QSize(48, 48));
button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
return button;
}
class PlayerTab : public TabBase
{
public:
explicit PlayerTab(MediaWidget *mediaWidget_);
~PlayerTab() { }
void activate()
{
layout()->addWidget(mediaWidget);
mediaWidget->setFocus();
}
private:
MediaWidget *mediaWidget;
};
PlayerTab::PlayerTab(MediaWidget *mediaWidget_) : mediaWidget(mediaWidget_)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
}
void MainWindow::run()
{
// Allow the user to enable or disable debugging
// We handle this before the other parameters, as it may affect some
// early debug messages
//
// --debug enables all debugging categories. It is possible to enable
// each category individually by calling Kaffeine with:
// QT_LOGGING_RULES="epg.debug=true" kaffeine
if (parser->isSet("debug")) {
QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, true);
QLoggingCategory::setFilterRules(QStringLiteral(FILTER_RULE));
} else {
QLoggingCategory::setFilterRules(QStringLiteral("kaffeine.*.debug=false"));
}
readSettings();
setAttribute(Qt::WA_DeleteOnClose, true);
// menu structure
QMenuBar *menuBar = QMainWindow::menuBar();
collection = new KActionCollection(this);
QMenu *menu = new QMenu(i18n("&File"), this);
menuBar->addMenu(menu);
QAction *action = KStandardAction::open(this, SLOT(open()), collection);
menu->addAction(collection->addAction(QLatin1String("file_open"), action));
action = new QAction(QIcon::fromTheme(QLatin1String("text-html"), QIcon(":text-html")),
i18nc("@action:inmenu", "Open URL..."), collection);
action->setShortcut(Qt::CTRL | Qt::Key_U);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openUrl()));
menu->addAction(collection->addAction(QLatin1String("file_open_url"), action));
actionOpenRecent = KStandardAction::openRecent(this, SLOT(openUrl(QUrl)), collection);
actionOpenRecent->loadEntries(KSharedConfig::openConfig()->group("Recent Files"));
menu->addAction(collection->addAction(QLatin1String("file_open_recent"), actionOpenRecent));
menu->addSeparator();
action = new QAction(QIcon::fromTheme(QLatin1String("media-optical-audio"), QIcon(":media-optical-audio")), i18n("Play Audio CD"), collection);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openAudioCd()));
menu->addAction(collection->addAction(QLatin1String("file_play_audiocd"), action));
- action = new QAction(QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical")), i18n("Play Video CD"), collection);
+ action = new QAction(QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical-video")), i18n("Play Video CD"), collection);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openVideoCd()));
menu->addAction(collection->addAction(QLatin1String("file_play_videocd"), action));
action = new QAction(QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical")), i18n("Play DVD"), collection);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openDvd()));
menu->addAction(collection->addAction(QLatin1String("file_play_dvd"), action));
action = new QAction(QIcon::fromTheme(QLatin1String("media-optical"), QIcon(":media-optical")), i18nc("@action:inmenu", "Play DVD Folder"),
collection);
connect(action, SIGNAL(triggered()), this, SLOT(playDvdFolder()));
menu->addAction(collection->addAction(QLatin1String("file_play_dvd_folder"), action));
menu->addSeparator();
action = KStandardAction::quit(this, SLOT(close()), collection);
menu->addAction(collection->addAction(QLatin1String("file_quit"), action));
QMenu *playerMenu = new QMenu(i18n("&Playback"), this);
menuBar->addMenu(playerMenu);
QMenu *playlistMenu = new QMenu(i18nc("menu bar", "Play&list"), this);
menuBar->addMenu(playlistMenu);
#if HAVE_DVB == 1
QMenu *dvbMenu = new QMenu(i18n("&Television"), this);
menuBar->addMenu(dvbMenu);
#endif /* HAVE_DVB == 1 */
menu = new QMenu(i18n("&Settings"), this);
menuBar->addMenu(menu);
action = KStandardAction::keyBindings(this, SLOT(configureKeys()), collection);
menu->addAction(collection->addAction(QLatin1String("settings_keys"), action));
action = KStandardAction::preferences(this, SLOT(configureKaffeine()), collection);
menu->addAction(collection->addAction(QLatin1String("settings_kaffeine"), action));
menuBar->addSeparator();
KHelpMenu *helpMenu = new KHelpMenu(this, *aboutData);
menuBar->addMenu(helpMenu->menu());
// navigation bar - keep in sync with TabIndex enum!
navigationBar = new QToolBar(QLatin1String("navigation_bar"));
this->addToolBar(Qt::LeftToolBarArea, navigationBar);
connect(navigationBar, SIGNAL(orientationChanged(Qt::Orientation)),
this, SLOT(navigationBarOrientationChanged(Qt::Orientation)));
tabBar = new QTabBar(navigationBar);
tabBar->addTab(QIcon::fromTheme(QLatin1String("start-here-kde"), QIcon(":start-here-kde")), i18n("Start"));
tabBar->addTab(QIcon::fromTheme(QLatin1String("kaffeine"), QIcon(":kaffeine")), i18n("Playback"));
tabBar->addTab(QIcon::fromTheme(QLatin1String("view-media-playlist"), QIcon(":view-media-playlist")), i18n("Playlist"));
#if HAVE_DVB == 1
tabBar->addTab(QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), i18n("Television"));
#endif /* HAVE_DVB == 1 */
tabBar->setShape(QTabBar::RoundedWest);
tabBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
connect(tabBar, SIGNAL(currentChanged(int)), this, SLOT(activateTab(int)));
navigationBar->addWidget(tabBar);
// control bar
controlBar = new QToolBar(QLatin1String("control_bar"));
this->addToolBar(Qt::BottomToolBarArea, controlBar);
controlBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
autoHideControlBar = false;
cursorHideTimer = new QTimer(this);
cursorHideTimer->setInterval(1500);
cursorHideTimer->setSingleShot(true);
connect(cursorHideTimer, SIGNAL(timeout()), this, SLOT(hideCursor()));
// main area
QWidget *widget = new QWidget(this);
stackedLayout = new StackedLayout(widget);
setCentralWidget(widget);
mediaWidget = new MediaWidget(playerMenu, controlBar, collection, widget);
connect(mediaWidget, SIGNAL(displayModeChanged()), this, SLOT(displayModeChanged()));
connect(mediaWidget, SIGNAL(changeCaption(QString)), this, SLOT(setWindowTitle(QString)));
// tabs - keep in sync with TabIndex enum!
TabBase *startTab = new StartTab(this);
tabs.append(startTab);
stackedLayout->addWidget(startTab);
playerTab = new PlayerTab(mediaWidget);
playerTab->activate();
tabs.append(playerTab);
stackedLayout->addWidget(playerTab);
playlistTab = new PlaylistTab(playlistMenu, collection, mediaWidget);
tabs.append(playlistTab);
stackedLayout->addWidget(playlistTab);
#if HAVE_DVB == 1
dvbTab = new DvbTab(dvbMenu, collection, mediaWidget);
connect(this, SIGNAL(mayCloseApplication(bool*,QWidget*)),
dvbTab, SLOT(mayCloseApplication(bool*,QWidget*)));
tabs.append(dvbTab);
stackedLayout->addWidget(dvbTab);
#endif /* HAVE_DVB == 1 */
currentTabIndex = StartTabId;
// actions also have to work if the menu bar is hidden (fullscreen)
collection->addAssociatedWidget(this);
// restore custom key bindings
collection->readSettings();
// Tray menu
menu = new QMenu(i18n("Kaffeine"), this);
action = new QAction(i18n("Play &File"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(open()));
menu->addAction(action);
action = new QAction(i18n("Play &Audio CD"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openAudioCd()));
menu->addAction(action);
action = new QAction(i18n("Play &Video CD"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openVideoCd()));
menu->addAction(action);
action = new QAction(i18n("Play &DVD"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(openDvd()));
menu->addAction(action);
#if HAVE_DVB == 1
action = new QAction(i18n("&Watch Digital TV"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(playDvb()));
menu->addAction(action);
#endif
action = new QAction(i18n("&Quit"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(close()));
menu->addAction(action);
// Tray Icon and its menu
QMenu *trayMenu = new QMenu(this);
trayIcon = new QSystemTrayIcon(this);
trayIcon->setContextMenu(trayMenu);
trayIcon->setIcon(QIcon::fromTheme(QLatin1String("kaffeine"), QIcon(":kaffeine")));
trayIcon->setToolTip(i18n("Kaffeine"));
trayIcon->setContextMenu(menu);
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayShowHide(QSystemTrayIcon::ActivationReason)) );
// make sure that the bars are visible (fullscreen -> quit -> restore -> hidden)
menuBar->show();
navigationBar->show();
controlBar->show();
trayIcon->show();
// workaround setAutoSaveSettings() which doesn't accept "IconOnly" as initial state
controlBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
// initialize random number generator
qsrand(QTime(0, 0, 0).msecsTo(QTime::currentTime()));
// initialize dbus objects
QDBusConnection::sessionBus().registerObject(QLatin1String("/"), new MprisRootObject(this),
QDBusConnection::ExportAllContents);
QDBusConnection::sessionBus().registerObject(QLatin1String("/Player"),
new MprisPlayerObject(this, mediaWidget, playlistTab, this),
QDBusConnection::ExportAllContents);
QDBusConnection::sessionBus().registerObject(QLatin1String("/TrackList"),
new MprisTrackListObject(playlistTab, this), QDBusConnection::ExportAllContents);
#if HAVE_DVB == 1
QDBusConnection::sessionBus().registerObject(QLatin1String("/Television"),
new DBusTelevisionObject(dvbTab, this), QDBusConnection::ExportAllContents);
#endif /* HAVE_DVB == 1 */
QDBusConnection::sessionBus().registerService(QLatin1String("org.mpris.kaffeine"));
show();
// set display mode
switch (Configuration::instance()->getStartupDisplayMode()) {
case Configuration::StartupNormalMode:
// nothing to do
break;
case Configuration::StartupMinimalMode:
mediaWidget->setDisplayMode(MediaWidget::MinimalMode);
break;
case Configuration::StartupFullScreenMode:
mediaWidget->setDisplayMode(MediaWidget::FullScreenMode);
break;
case Configuration::StartupRememberLastSetting: {
int value = KSharedConfig::openConfig()->group("MainWindow").readEntry("DisplayMode", 0);
switch (value) {
case 0:
// nothing to do
break;
case 1:
mediaWidget->setDisplayMode(MediaWidget::MinimalMode);
break;
case 2:
mediaWidget->setDisplayMode(MediaWidget::FullScreenMode);
break;
}
break;
}
}
parseArgs();
}
MainWindow::~MainWindow()
{
actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group("Recent Files"));
if (!temporaryUrls.isEmpty()) {
KIO::del(temporaryUrls);
}
int value = 0;
switch (mediaWidget->getDisplayMode()) {
case MediaWidget::NormalMode: value = 0; break;
case MediaWidget::MinimalMode: value = 1; break;
case MediaWidget::FullScreenMode: value = 2; break;
case MediaWidget::FullScreenReturnToMinimalMode: value = 2; break;
}
KSharedConfig::openConfig()->group("MainWindow").writeEntry("DisplayMode", value);
}
void MainWindow::close()
{
bool ok = true;
#if HAVE_DVB == 1
dvbTab->mayCloseApplication(&ok, mediaWidget);
#endif /* HAVE_DVB == 1 */
if (ok)
QCoreApplication::exit(0);
}
void MainWindow::readSettings()
{
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
if (geometry.isEmpty()) {
const QRect availableGeometry = QApplication::desktop()->availableGeometry(this);
resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
move((availableGeometry.width() - width()) / 2,
(availableGeometry.height() - height()) / 2);
} else {
restoreGeometry(geometry);
}
}
void MainWindow::writeSettings()
{
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
settings.setValue("geometry", saveGeometry());
}
MainWindow::MainWindow(KAboutData *aboutData, QCommandLineParser *parser)
{
this->aboutData = aboutData;
this->parser = parser;
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("d") << QLatin1String("debug"), i18n("Enable all debug messages. Please notice that Kaffeine also allows enabling debug messages per category, by using the environment var:\nQT_LOGGING_RULES=kaffeine.category.debug=true\nwhere 'category' can be:\n" CATEGORIES)));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("tempfile"), i18n("The files/URLs opened by the application will be deleted after use")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("f") << QLatin1String("fullscreen"), i18n("Start in full screen mode")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("audiocd"), i18n("Play Audio CD")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("videocd"), i18n("Play Video CD")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("dvd"), i18n("Play DVD")));
#if HAVE_DVB == 1
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("dumpdvb"), i18nc("command line option", "Dump dvb data (debug option)")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("channel"), i18nc("command line option", "Play TV channel"), QLatin1String("name / number")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("tv"), i18nc("command line option", "(deprecated option)"), QLatin1String("channel")));
parser->addOption(QCommandLineOption(QStringList() << QLatin1String("lastchannel"), i18nc("command line option", "Play last tuned TV channel")));
#endif
parser->addPositionalArgument(QLatin1String("[file]"), i18n("Files or URLs to play"));
}
void MainWindow::parseArgs(const QString workingDirectory)
{
if (parser->isSet("fullscreen")) {
mediaWidget->setDisplayMode(MediaWidget::FullScreenMode);
}
if (parser->isSet("audiocd")) {
if (parser->positionalArguments().count() > 0) {
openAudioCd(parser->positionalArguments().first());
} else {
openAudioCd();
}
return;
}
if (parser->isSet("videocd")) {
if (parser->positionalArguments().count() > 0) {
openVideoCd(parser->positionalArguments().first());
} else {
openVideoCd();
}
return;
}
if (parser->isSet("dvd")) {
if (parser->positionalArguments().count() > 0) {
openDvd(parser->positionalArguments().first());
} else {
openDvd();
}
return;
}
#if HAVE_DVB == 1
QString channel;
if (parser->isSet("dumpdvb")) {
dvbTab->enableDvbDump();
}
channel = parser->value("channel");
if (!channel.isEmpty()) {
activateTab(DvbTabId);
dvbTab->playChannel(channel);
return;
}
channel = parser->value("tv");
if (!channel.isEmpty()) {
activateTab(DvbTabId);
dvbTab->playChannel(channel);
return;
}
if (parser->isSet("lastchannel")) {
activateTab(DvbTabId);
dvbTab->playLastChannel();
return;
}
#endif /* HAVE_DVB == 1 */
if (parser->positionalArguments().count() > 0) {
QList urls;
for (int i = 0; i < parser->positionalArguments().count(); ++i) {
QUrl url = QUrl::fromUserInput(parser->positionalArguments().at(i), workingDirectory);
if (url.isValid()) {
urls.append(url);
}
}
if (parser->isSet("tempfile")) {
temporaryUrls.append(urls);
}
if (urls.size() >= 2) {
activateTab(PlaylistTabId);
playlistTab->appendToVisiblePlaylist(urls, true);
} else if (!urls.isEmpty()) {
openUrl(urls.at(0));
}
}
}
void MainWindow::displayModeChanged()
{
MediaWidget::DisplayMode displayMode = mediaWidget->getDisplayMode();
switch (displayMode) {
case MediaWidget::FullScreenMode:
case MediaWidget::FullScreenReturnToMinimalMode:
setWindowState(windowState() | Qt::WindowFullScreen);
break;
case MediaWidget::MinimalMode:
case MediaWidget::NormalMode:
setWindowState(windowState() & (~Qt::WindowFullScreen));
break;
}
switch (displayMode) {
case MediaWidget::FullScreenMode:
case MediaWidget::FullScreenReturnToMinimalMode:
case MediaWidget::MinimalMode:
menuBar()->hide();
navigationBar->hide();
controlBar->hide();
autoHideControlBar = true;
cursorHideTimer->start();
break;
case MediaWidget::NormalMode:
menuBar()->show();
navigationBar->show();
controlBar->show();
autoHideControlBar = false;
cursorHideTimer->stop();
unsetCursor();
break;
}
tabs.at(currentTabIndex)->toggleDisplayMode(displayMode);
}
void MainWindow::trayShowHide(QSystemTrayIcon::ActivationReason reason)
{
if (reason != QSystemTrayIcon::DoubleClick)
return;
if (isVisible())
hide();
else {
show();
raise();
setFocus();
}
}
void MainWindow::open()
{
if (isMinimized())
showNormal();
QList urls = QFileDialog::getOpenFileUrls(this, i18nc("@title:window", "Open files"), QUrl(), MediaWidget::extensionFilter());
// trayIcon->showMessage("Open", "Opening file(s)");
if (urls.size() >= 2) {
activateTab(PlaylistTabId);
playlistTab->appendToVisiblePlaylist(urls, true);
} else if (!urls.isEmpty()) {
openUrl(urls.at(0));
}
}
void MainWindow::openUrl()
{
openUrl(QInputDialog::getText(this, i18nc("@title:window", "Open URL"), i18n("Enter a URL:")));
}
void MainWindow::openUrl(const QUrl &url)
{
if (!url.isValid()) {
return;
}
// we need to copy "url" because addUrl() may invalidate it
QUrl copy(url);
actionOpenRecent->addUrl(copy); // moves the url to the top of the list
if (currentTabIndex != PlaylistTabId) {
activateTab(PlayerTabId);
}
playlistTab->appendToVisiblePlaylist(QList() << copy, true);
}
void MainWindow::openAudioCd(const QString &device)
{
if (isMinimized())
showNormal();
activateTab(PlayerTabId);
mediaWidget->playAudioCd(device);
}
void MainWindow::openVideoCd(const QString &device)
{
if (isMinimized())
showNormal();
activateTab(PlayerTabId);
mediaWidget->playVideoCd(device);
}
void MainWindow::openDvd(const QString &device)
{
if (isMinimized())
showNormal();
activateTab(PlayerTabId);
mediaWidget->playDvd(device);
}
void MainWindow::playDvdFolder()
{
QString folder = QFileDialog::getExistingDirectory(this, QString());
if (!folder.isEmpty()) {
openDvd(folder);
}
}
void MainWindow::playDvb()
{
if (isMinimized())
showNormal();
activateTab(DvbTabId);
dvbTab->playLastChannel();
}
void MainWindow::configureKeys()
{
KShortcutsDialog::configure(collection);
}
void MainWindow::configureKaffeine()
{
QDialog *dialog = new ConfigurationDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->setModal(true);
dialog->show();
}
void MainWindow::navigationBarOrientationChanged(Qt::Orientation orientation)
{
if (orientation == Qt::Horizontal) {
tabBar->setShape(QTabBar::RoundedNorth);
} else {
tabBar->setShape(QTabBar::RoundedWest);
}
}
void MainWindow::activateTab(int tabIndex)
{
currentTabIndex = tabIndex;
tabBar->setCurrentIndex(tabIndex);
if (!autoHideControlBar) {
stackedLayout->setCurrentIndex(currentTabIndex);
tabs.at(currentTabIndex)->activate();
}
}
void MainWindow::hideCursor()
{
setCursor(Qt::BlankCursor);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
bool ok = true;
emit mayCloseApplication(&ok, this);
if (!ok) {
event->ignore();
} else {
writeSettings();
QMainWindow::closeEvent(event);
}
}
bool MainWindow::event(QEvent *event)
{
bool retVal = QMainWindow::event(event); // this has to be done before calling setVisible()
// FIXME we depend on QEvent::HoverMove (instead of QEvent::MouseMove)
// but the latter depends on mouse tracking being enabled on this widget
// and all its children (especially the video widget) ...
switch (event->type()) {
case QEvent::Wheel: {
QWheelEvent *wheel = static_cast(event);
int delta = (wheel->pixelDelta().y() < 0) ? -1 : 1;
mediaWidget->setVolume(mediaWidget->getVolume() + delta);
break;
}
case QEvent::HoverMove: {
int x = reinterpret_cast (event)->pos().x();
int y = reinterpret_cast (event)->pos().y();
if ((y < 0) || (y >= height()) ||
(x < 0) || (x >= width())) {
// QHoverEvent sometimes reports quite strange coordinates - ignore them
return retVal;
}
if (autoHideControlBar) {
cursorHideTimer->stop();
unsetCursor();
switch (toolBarArea(controlBar)) {
case Qt::TopToolBarArea:
controlBar->setVisible(y < 60);
break;
case Qt::BottomToolBarArea:
controlBar->setVisible(y >= (height() - 60));
menuBar()->setVisible(y < 60);
break;
default:
break;
}
if (controlBar->isHidden() || menuBar()->isHidden()) {
cursorHideTimer->start();
}
}
tabs.at(currentTabIndex)->mouse_move(x, y);
break;
}
default:
break;
}
return retVal;
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
mediaWidget->setDisplayMode(MediaWidget::NormalMode);
}
QMainWindow::keyPressEvent(event);
}
void MainWindow::leaveEvent(QEvent *event)
{
if (autoHideControlBar) {
controlBar->setVisible(false);
}
QMainWindow::leaveEvent(event);
}
diff --git a/src/playlist/playlisttab.cpp b/src/playlist/playlisttab.cpp
index 99839fb..01f1290 100644
--- a/src/playlist/playlisttab.cpp
+++ b/src/playlist/playlisttab.cpp
@@ -1,874 +1,874 @@
/*
* playlisttab.cpp
*
* Copyright (C) 2009-2011 Christoph Pfister
*
* 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.
*/
#include "../log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "playlistmodel.h"
#include "playlisttab.h"
PlaylistBrowserModel::PlaylistBrowserModel(PlaylistModel *playlistModel_,
Playlist *temporaryPlaylist, QObject *parent) : QAbstractListModel(parent),
playlistModel(playlistModel_), currentPlaylist(-1)
{
playlists.append(temporaryPlaylist);
QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/playlistsK4"));
if (!file.open(QIODevice::ReadOnly)) {
file.setFileName(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/playlists"));
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(logPlaylist, "Cannot open file %s", qPrintable(file.fileName()));
return;
}
}
QDataStream stream(&file);
stream.setVersion(QDataStream::Qt_4_4);
unsigned int version;
stream >> version;
bool hasMetadata = true;
bool hasSubtitles = true;
if (version == 0xc39637a1) {
// compatibility code
hasMetadata = false;
hasSubtitles = false;
} else if (version == 0x2e00f3ea) {
// compatibility code
hasSubtitles = false;
} else if (version != 0x361c4a3c) {
qCWarning(logPlaylist, "Cannot read file %s", qPrintable(file.fileName()));
return;
}
while (!stream.atEnd()) {
Playlist *playlist = new Playlist();
stream >> playlist->title;
QString urlString;
stream >> urlString;
playlist->url = urlString;
int count;
stream >> count;
for (int i = 0; (i < count) && !stream.atEnd(); ++i) {
PlaylistTrack track;
stream >> urlString;
track.url = urlString;
if (hasMetadata) {
stream >> track.title;
stream >> track.artist;
stream >> track.album;
stream >> track.trackNumber;
stream >> track.length;
} else {
track.title = track.url.fileName();
}
if (hasSubtitles) {
QStringList subtitleStrings;
stream >> subtitleStrings;
foreach (const QString &subtitleString, subtitleStrings) {
track.subtitles.append(subtitleString);
}
stream >> track.currentSubtitle;
}
playlist->tracks.append(track);
}
if (stream.status() != QDataStream::Ok) {
qCWarning(logPlaylist, "Cannot read file %s", qPrintable(file.fileName()));
delete playlist;
break;
}
playlists.append(playlist);
}
}
PlaylistBrowserModel::~PlaylistBrowserModel()
{
QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/playlistsK4"));
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qCWarning(logPlaylist, "Cannot open file %s", qPrintable(file.fileName()));
return;
}
QDataStream stream(&file);
stream.setVersion(QDataStream::Qt_4_4);
int version = 0x361c4a3c;
stream << version;
for (int i = 1; i < playlists.size(); ++i) {
const Playlist *playlist = playlists.at(i);
stream << playlist->title;
stream << playlist->url.url();
stream << playlist->tracks.size();
foreach (const PlaylistTrack &track, playlist->tracks) {
stream << track.url.url();
stream << track.title;
stream << track.artist;
stream << track.album;
stream << track.trackNumber;
stream << track.length;
QStringList subtitleStrings;
for (int j = 0; j < track.subtitles.size(); ++j) {
const QUrl &url = track.subtitles.at(j);
subtitleStrings.append(url.url());
}
stream << subtitleStrings;
stream << track.currentSubtitle;
}
}
qDeleteAll(playlists);
}
int PlaylistBrowserModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return playlists.size();
}
QVariant PlaylistBrowserModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DecorationRole) {
if (index.row() == currentPlaylist) {
return QIcon::fromTheme(QLatin1String("arrow-right"), QIcon(":arrow-right"));
}
} else if (role == Qt::DisplayRole) {
return playlists.at(index.row())->title;
}
return QVariant();
}
bool PlaylistBrowserModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid()) {
return false;
}
if (row == 0) {
++row;
if ((--count) == 0) {
return false;
}
}
beginRemoveRows(QModelIndex(), row, row + count - 1);
Playlist *visiblePlaylist = playlistModel->getVisiblePlaylist();
for (int i = row; i < (row + count); ++i) {
if (playlists.at(i) == visiblePlaylist) {
if ((row + count) < playlists.size()) {
playlistModel->setVisiblePlaylist(playlists.at(row + count));
} else {
playlistModel->setVisiblePlaylist(playlists.at(row - 1));
}
}
}
QList::Iterator begin = playlists.begin() + row;
QList::Iterator end = begin + count;
qDeleteAll(begin, end);
playlists.erase(begin, end);
if (currentPlaylist >= row) {
if (currentPlaylist >= (row + count)) {
currentPlaylist -= count;
} else {
currentPlaylist = -1;
emit playTrack(NULL, -1);
}
}
endRemoveRows();
return true;
}
Qt::ItemFlags PlaylistBrowserModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
}
bool PlaylistBrowserModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
QString title = value.toString();
if (title.isEmpty()) {
return false;
}
playlists.at(index.row())->title = title;
emit dataChanged(index, index);
return true;
}
return false;
}
void PlaylistBrowserModel::append(Playlist *playlist)
{
beginInsertRows(QModelIndex(), playlists.size(), playlists.size());
playlists.append(playlist);
endInsertRows();
}
Playlist *PlaylistBrowserModel::getPlaylist(int row) const
{
return playlists.at(row);
}
void PlaylistBrowserModel::setCurrentPlaylist(Playlist *playlist)
{
int oldPlaylist = currentPlaylist;
currentPlaylist = playlists.indexOf(playlist);
if (oldPlaylist == currentPlaylist) {
return;
}
if (oldPlaylist != -1) {
playlistModel->setCurrentTrack(playlists.at(oldPlaylist), -1);
QModelIndex modelIndex = index(oldPlaylist, 0);
emit dataChanged(modelIndex, modelIndex);
}
if (currentPlaylist != -1) {
QModelIndex modelIndex = index(currentPlaylist, 0);
emit dataChanged(modelIndex, modelIndex);
}
}
Playlist *PlaylistBrowserModel::getCurrentPlaylist() const
{
if (currentPlaylist >= 0) {
return playlists.at(currentPlaylist);
} else {
return playlistModel->getVisiblePlaylist();
}
}
class PlaylistBrowserView : public QListView
{
public:
explicit PlaylistBrowserView(QWidget *parent) : QListView(parent) { }
~PlaylistBrowserView() { }
protected:
void keyPressEvent(QKeyEvent *event);
};
void PlaylistBrowserView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete) {
QModelIndexList selectedRows = selectionModel()->selectedRows();
qSort(selectedRows);
for (int i = selectedRows.size() - 1; i >= 0; --i) {
// FIXME compress
model()->removeRows(selectedRows.at(i).row(), 1);
}
return;
}
QListView::keyPressEvent(event);
}
PlaylistView::PlaylistView(QWidget *parent) : QTreeView(parent)
{
}
PlaylistView::~PlaylistView()
{
}
void PlaylistView::removeSelectedRows()
{
QModelIndexList selectedRows = selectionModel()->selectedRows();
qSort(selectedRows);
for (int i = selectedRows.size() - 1; i >= 0; --i) {
// FIXME compress
model()->removeRows(selectedRows.at(i).row(), 1);
}
}
void PlaylistView::contextMenuEvent(QContextMenuEvent *event)
{
if (!currentIndex().isValid()) {
return;
}
QMenu::exec(actions(), event->globalPos());
}
void PlaylistView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete) {
removeSelectedRows();
return;
}
QTreeView::keyPressEvent(event);
}
PlaylistTab::PlaylistTab(QMenu *menu, KActionCollection *collection, MediaWidget *mediaWidget_) :
mediaWidget(mediaWidget_)
{
Playlist *temporaryPlaylist = new Playlist();
temporaryPlaylist->title = i18nc("playlist browser", "Temporary Playlist");
playlistModel = new PlaylistModel(temporaryPlaylist, this);
connect(playlistModel, SIGNAL(playTrack(Playlist*,int)),
this, SLOT(playTrack(Playlist*,int)));
connect(playlistModel, SIGNAL(appendPlaylist(Playlist*,bool)),
this, SLOT(appendPlaylist(Playlist*,bool)));
playlistBrowserModel = new PlaylistBrowserModel(playlistModel, temporaryPlaylist, this);
playlistModel->setVisiblePlaylist(playlistBrowserModel->getPlaylist(0));
connect(playlistBrowserModel, SIGNAL(playTrack(Playlist*,int)),
this, SLOT(playTrack(Playlist*,int)));
connect(mediaWidget, SIGNAL(playlistPrevious()), this, SLOT(playPreviousTrack()));
connect(mediaWidget, SIGNAL(playlistNext()), this, SLOT(playNextTrack()));
connect(mediaWidget, SIGNAL(playlistUrlsDropped(QList)),
this, SLOT(appendUrls(QList)));
connect(mediaWidget, SIGNAL(playlistTrackLengthChanged(int)),
this, SLOT(updateTrackLength(int)));
connect(mediaWidget,
SIGNAL(playlistTrackMetadataChanged(QMap)),
this, SLOT(updateTrackMetadata(QMap)));
repeatAction = new QAction(QIcon::fromTheme(QLatin1String("media-playlist-repeat"), QIcon(":media-playlist-repeat")),
i18nc("playlist menu", "Repeat"), this);
repeatAction->setCheckable(true);
menu->addAction(collection->addAction(QLatin1String("playlist_repeat"), repeatAction));
randomAction = new QAction(QIcon::fromTheme(QLatin1String("media-playlist-shuffle"), QIcon(":media-playlist-shuffle")),
i18nc("playlist menu", "Random"), this);
randomAction->setCheckable(true);
menu->addAction(collection->addAction(QLatin1String("playlist_random"), randomAction));
QAction *addSubtitleAction =
new QAction(QIcon::fromTheme(QLatin1String("application-x-subrip"), QIcon(":application-x-subrip")), i18n("Add Subtitle"), this);
collection->addAction(QLatin1String("playlist_add_subtitle"), addSubtitleAction);
QAction *removeTrackAction =
new QAction(QIcon::fromTheme(QLatin1String("edit-delete"), QIcon(":edit-delete")), i18nc("@action", "Remove"), this);
collection->addAction(QLatin1String("playlist_remove_track"), removeTrackAction);
QAction *clearAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear-list"), QIcon(":edit-clear-list")),
i18nc("remove all items from a list", "Clear"), this);
connect(clearAction, SIGNAL(triggered(bool)), playlistModel, SLOT(clearVisiblePlaylist()));
menu->addAction(collection->addAction(QLatin1String("playlist_clear"), clearAction));
menu->addSeparator();
- QAction *newAction = new QAction(QIcon::fromTheme(QLatin1String("list-add"), QIcon(":list-add")), i18nc("@action", "New"), this);
+ QAction *newAction = new QAction(QIcon::fromTheme(QLatin1String("list-add"), QIcon(":list-add")), i18nc("@action:inmenu playlist", "New"), this);
connect(newAction, SIGNAL(triggered(bool)), this, SLOT(newPlaylist()));
menu->addAction(collection->addAction(QLatin1String("playlist_new"), newAction));
QAction *renameAction = new QAction(QIcon::fromTheme(QLatin1String("edit-rename"), QIcon(":edit-rename")),
i18nc("rename an entry in a list", "Rename"), this);
connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(renamePlaylist()));
menu->addAction(collection->addAction(QLatin1String("playlist_rename"), renameAction));
QAction *removePlaylistAction =
new QAction(QIcon::fromTheme(QLatin1String("edit-delete"), QIcon(":edit-delete")), i18nc("@action", "Remove"), this);
connect(removePlaylistAction, SIGNAL(triggered(bool)), this, SLOT(removePlaylist()));
menu->addAction(collection->addAction(QLatin1String("playlist_remove"), removePlaylistAction));
QAction *savePlaylistAction = KStandardAction::save(this, SLOT(savePlaylist()), this);
menu->addAction(collection->addAction(QLatin1String("playlist_save"), savePlaylistAction));
QAction *savePlaylistAsAction =
KStandardAction::saveAs(this, SLOT(savePlaylistAs()), this);
menu->addAction(collection->addAction(QLatin1String("playlist_save_as"), savePlaylistAsAction));
QBoxLayout *widgetLayout = new QHBoxLayout(this);
widgetLayout->setMargin(0);
QSplitter *horizontalSplitter = new QSplitter(this);
widgetLayout->addWidget(horizontalSplitter);
QSplitter *verticalSplitter = new QSplitter(Qt::Vertical, horizontalSplitter);
QWidget *widget = new QWidget(verticalSplitter);
QBoxLayout *sideLayout = new QVBoxLayout(widget);
sideLayout->setMargin(0);
QBoxLayout *boxLayout = new QHBoxLayout();
QToolButton *toolButton = new QToolButton(widget);
toolButton->setDefaultAction(newAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(renameAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(removePlaylistAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(savePlaylistAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(savePlaylistAsAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
boxLayout->addStretch();
sideLayout->addLayout(boxLayout);
playlistBrowserView = new PlaylistBrowserView(widget);
playlistBrowserView->addAction(newAction);
playlistBrowserView->addAction(renameAction);
playlistBrowserView->addAction(removePlaylistAction);
playlistBrowserView->addAction(savePlaylistAction);
playlistBrowserView->addAction(savePlaylistAsAction);
playlistBrowserView->setContextMenuPolicy(Qt::ActionsContextMenu);
playlistBrowserView->setModel(playlistBrowserModel);
connect(playlistBrowserView, SIGNAL(activated(QModelIndex)),
this, SLOT(playlistActivated(QModelIndex)));
sideLayout->addWidget(playlistBrowserView);
// KFileWidget creates a local event loop which can cause bad side
// effects (because the main window isn't fully constructed yet)
fileWidgetSplitter = verticalSplitter;
QTimer::singleShot(0, this, SLOT(createFileWidget()));
verticalSplitter = new QSplitter(Qt::Vertical, horizontalSplitter);
widget = new QWidget(verticalSplitter);
sideLayout = new QVBoxLayout(widget);
sideLayout->setMargin(0);
boxLayout = new QHBoxLayout();
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(repeatAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(randomAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(removeTrackAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
toolButton = new QToolButton(widget);
toolButton->setDefaultAction(clearAction);
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
boxLayout->addWidget(toolButton);
boxLayout->addStretch();
sideLayout->addLayout(boxLayout);
playlistView = new PlaylistView(widget);
playlistView->setAlternatingRowColors(true);
playlistView->setDragDropMode(QAbstractItemView::DragDrop);
playlistView->setRootIsDecorated(false);
playlistView->setSelectionMode(QAbstractItemView::ExtendedSelection);
playlistView->setModel(playlistModel);
playlistView->sortByColumn(-1, Qt::AscendingOrder);
playlistView->setSortingEnabled(true);
playlistView->addAction(addSubtitleAction);
connect(addSubtitleAction, SIGNAL(triggered(bool)), this, SLOT(addSubtitle()));
playlistView->addAction(removeTrackAction);
connect(removeTrackAction, SIGNAL(triggered(bool)),
playlistView, SLOT(removeSelectedRows()));
connect(playlistView, SIGNAL(activated(QModelIndex)),
this, SLOT(playTrack(QModelIndex)));
sideLayout->addWidget(playlistView);
QWidget *mediaContainer = new QWidget(verticalSplitter);
mediaLayout = new QHBoxLayout(mediaContainer);
mediaLayout->setMargin(0);
verticalSplitter->setStretchFactor(1, 1);
horizontalSplitter->setStretchFactor(1, 1);
}
PlaylistTab::~PlaylistTab()
{
}
void PlaylistTab::appendToCurrentPlaylist(const QList &urls, bool playImmediately)
{
playlistModel->appendUrls(playlistBrowserModel->getCurrentPlaylist(), urls,
playImmediately);
}
void PlaylistTab::appendToVisiblePlaylist(const QList &urls, bool playImmediately)
{
playlistModel->appendUrls(playlistModel->getVisiblePlaylist(), urls, playImmediately);
}
void PlaylistTab::removeTrack(int row)
{
Playlist *currentPlaylist = playlistBrowserModel->getCurrentPlaylist();
if ((row >= 0) && (row < currentPlaylist->tracks.size())) {
playlistModel->removeRows(currentPlaylist, row, 1);
}
}
void PlaylistTab::setRandom(bool random)
{
randomAction->setChecked(random);
}
void PlaylistTab::setRepeat(bool repeat)
{
repeatAction->setChecked(repeat);
}
int PlaylistTab::getCurrentTrack() const
{
const Playlist *currentPlaylist = playlistBrowserModel->getCurrentPlaylist();
if (currentPlaylist->currentTrack >= 0) {
return currentPlaylist->currentTrack;
} else {
return 0;
}
}
int PlaylistTab::getTrackCount() const
{
return playlistBrowserModel->getCurrentPlaylist()->tracks.size();
}
bool PlaylistTab::getRandom() const
{
return randomAction->isChecked();
}
bool PlaylistTab::getRepeat() const
{
return repeatAction->isChecked();
}
void PlaylistTab::createFileWidget()
{
KFileWidget *fileWidget = new KFileWidget(QUrl(), fileWidgetSplitter);
fileWidget->setFilter(MediaWidget::extensionFilter());
fileWidget->setMode(KFile::Files | KFile::ExistingOnly);
fileWidgetSplitter->setStretchFactor(1, 1);
// KFileWidget creates a QUrlComboBox without layout (!), which steals the focus:
// kDebug() << QApplication::focusWidget();
// kDebug() << QApplication::focusWidget()->layout();
// Let's reclaim the focus (and give it back to the main window).
// FIXME report issue
window()->setFocus();
}
void PlaylistTab::newPlaylist()
{
Playlist *playlist = new Playlist();
playlist->title = i18nc("playlist browser", "Unnamed Playlist");
playlistBrowserModel->append(playlist);
}
void PlaylistTab::renamePlaylist()
{
QModelIndex index = playlistBrowserView->currentIndex();
if (!index.isValid() || (index.row() == 0)) {
return;
}
playlistBrowserView->edit(index);
}
void PlaylistTab::removePlaylist()
{
QModelIndexList selectedRows = playlistBrowserView->selectionModel()->selectedRows();
qSort(selectedRows);
for (int i = selectedRows.size() - 1; i >= 0; --i) {
// FIXME compress
playlistBrowserModel->removeRows(selectedRows.at(i).row(), 1);
}
}
void PlaylistTab::savePlaylist()
{
savePlaylist(false);
}
void PlaylistTab::savePlaylistAs()
{
savePlaylist(true);
}
void PlaylistTab::addSubtitle()
{
QModelIndexList selectedRows = playlistView->selectionModel()->selectedRows();
if (selectedRows.size() != 1) {
return;
}
int row = selectedRows.at(0).row();
Playlist *playlist = playlistModel->getVisiblePlaylist();
QList urls = QFileDialog::getOpenFileUrls(this, "", QUrl(), subtitleExtensionFilter());
if ((row < playlist->tracks.size()) && !urls.isEmpty()) {
PlaylistTrack &track = playlist->tracks[row];
track.subtitles += urls;
if (track.subtitles.size() == urls.size()) {
track.currentSubtitle = 0;
}
if ((playlist == playlistBrowserModel->getCurrentPlaylist()) &&
(playlist->currentTrack == row)) {
// FIXME !
}
}
}
void PlaylistTab::playlistActivated(const QModelIndex &index)
{
playlistModel->setVisiblePlaylist(playlistBrowserModel->getPlaylist(index.row()));
}
void PlaylistTab::playPreviousTrack()
{
Playlist *currentPlaylist = playlistBrowserModel->getCurrentPlaylist();
playTrack(currentPlaylist, currentPlaylist->currentTrack - 1);
}
void PlaylistTab::playCurrentTrack()
{
Playlist *currentPlaylist = playlistBrowserModel->getCurrentPlaylist();
if (currentPlaylist->currentTrack >= 0) {
playTrack(currentPlaylist, currentPlaylist->currentTrack);
} else {
playNextTrack();
}
}
void PlaylistTab::playNextTrack()
{
Playlist *currentPlaylist = playlistBrowserModel->getCurrentPlaylist();
if (randomAction->isChecked()) {
int size = currentPlaylist->tracks.size();
if (size < 2) {
playTrack(currentPlaylist, 0);
} else if (currentPlaylist->currentTrack != -1) {
int track = (qrand() % (size - 1));
if (track >= currentPlaylist->currentTrack) {
++track;
}
playTrack(currentPlaylist, track);
} else {
playTrack(currentPlaylist, qrand() % size);
}
} else if (((currentPlaylist->currentTrack + 1) == currentPlaylist->tracks.size()) &&
repeatAction->isChecked()) {
playTrack(currentPlaylist, 0);
} else {
playTrack(currentPlaylist, currentPlaylist->currentTrack + 1);
}
}
void PlaylistTab::activate()
{
mediaLayout->addWidget(mediaWidget);
}
void PlaylistTab::playTrack(const QModelIndex &index)
{
playTrack(playlistModel->getVisiblePlaylist(), index.row());
}
void PlaylistTab::playTrack(Playlist *playlist, int track)
{
if ((track < 0) || (playlist == NULL) || (track >= playlist->tracks.size())) {
track = -1;
}
if (track != -1) {
PlaylistTrack &playlistTrack = playlist->tracks[track];
QUrl subtitleUrl;
if ((playlistTrack.currentSubtitle >= 0) &&
(playlistTrack.currentSubtitle < playlistTrack.subtitles.size())) {
subtitleUrl = playlistTrack.subtitles.at(playlistTrack.currentSubtitle);
} else if (playlistTrack.subtitles.isEmpty()) {
// check whether there's a possible subtitle file candidate
QString localFile = playlistTrack.url.toLocalFile();
localFile.truncate(localFile.lastIndexOf(QLatin1Char('.')));
if (!localFile.isEmpty()) {
if (QFile::exists(localFile + QLatin1String(".asc"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".asc"));
} else if (QFile::exists(localFile + QLatin1String(".smi"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".smi"));
} else if (QFile::exists(localFile + QLatin1String(".srt"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".srt"));
} else if (QFile::exists(localFile + QLatin1String(".ssa"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".ssa"));
} else if (QFile::exists(localFile + QLatin1String(".sub"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".sub"));
} else if (QFile::exists(localFile + QLatin1String(".txt"))) {
subtitleUrl = QUrl::fromLocalFile(localFile + QLatin1String(".txt"));
}
}
if (subtitleUrl.isValid()) {
playlistTrack.subtitles += subtitleUrl;
playlistTrack.currentSubtitle = 0;
}
}
mediaWidget->play(playlistTrack.url, subtitleUrl);
// FIXME !
playlistBrowserModel->setCurrentPlaylist(playlist);
} else {
mediaWidget->stop();
}
if (playlist != NULL) {
playlistModel->setCurrentTrack(playlist, track);
}
}
void PlaylistTab::appendUrls(const QList &urls)
{
playlistModel->appendUrls(playlistModel->getVisiblePlaylist(), urls, true);
}
void PlaylistTab::appendPlaylist(Playlist *playlist, bool playImmediately)
{
playlistBrowserModel->append(playlist);
if (playImmediately) {
playlistBrowserModel->setCurrentPlaylist(playlist);
playlistModel->setVisiblePlaylist(playlist);
playNextTrack();
}
}
void PlaylistTab::updateTrackLength(int length)
{
playlistModel->updateTrackLength(playlistBrowserModel->getCurrentPlaylist(), length);
}
void PlaylistTab::updateTrackMetadata(const QMap &metadata)
{
playlistModel->updateTrackMetadata(playlistBrowserModel->getCurrentPlaylist(), metadata);
}
QString PlaylistTab::subtitleExtensionFilter()
{
return QString(QLatin1String("*.asc *.smi *.srt *.ssa *.sub *.txt|")) +
i18nc("file filter", "Subtitle Files");
}
void PlaylistTab::savePlaylist(bool askName)
{
QModelIndex index = playlistBrowserView->currentIndex();
if (!index.isValid()) {
return;
}
Playlist *playlist = playlistBrowserModel->getPlaylist(index.row());
QUrl url = playlist->url;
if (askName || !url.isValid() ||
url.fileName().endsWith(QLatin1String(".kaffeine"), Qt::CaseInsensitive)) {
url = QFileDialog::getSaveFileUrl(this, "", QUrl(), i18nc("file filter",
"*.xspf|XSPF Playlist\n*.m3u|M3U Playlist\n*.pls|PLS Playlist"));
if (!url.isValid()) {
return;
}
playlist->url = url;
}
QString fileName = url.fileName();
Playlist::Format format = Playlist::Invalid;
if (fileName.endsWith(QLatin1String(".m3u"), Qt::CaseInsensitive)) {
format = Playlist::M3U;
} else if (fileName.endsWith(QLatin1String(".pls"), Qt::CaseInsensitive)) {
format = Playlist::PLS;
} else if (fileName.endsWith(QLatin1String(".xspf"), Qt::CaseInsensitive)) {
format = Playlist::XSPF;
}
if (format != Playlist::Invalid) {
playlist->save(format);
}
}