QML Episode List Test UI
This commit is contained in:
parent
00a876079d
commit
e0d45914d7
14 changed files with 242 additions and 41 deletions
2
Makefile
2
Makefile
|
@ -74,7 +74,7 @@ test:
|
|||
|
||||
qmltest:
|
||||
@echo -ne '\033]0;gPodder/QML console\007'
|
||||
$(BINFILE) --qml --verbose
|
||||
$(BINFILE) --qml --verbose -- -graphicssystem opengl
|
||||
|
||||
unittest:
|
||||
PYTHONPATH=src/ $(PYTHON) -m gpodder.unittests
|
||||
|
|
30
data/ui/qml/EpisodeItem.qml
Normal file
30
data/ui/qml/EpisodeItem.qml
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Qt 4.7
|
||||
|
||||
Image {
|
||||
width: parent.width
|
||||
source: 'episodeList/bg.png'
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
source: 'episodeList/' + model.episode.qfiletype + '.png'
|
||||
width: 40
|
||||
height: 40
|
||||
opacity: model.episode.qdownloaded?1:.1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
}
|
||||
|
||||
ShadowText {
|
||||
text: model.episode.qtitle
|
||||
color: model.episode.qnew?"white":"#888"
|
||||
font.pointSize: 16
|
||||
font.bold: false //model.episode.qnew
|
||||
anchors.left: icon.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 15
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
79
data/ui/qml/EpisodeList.qml
Normal file
79
data/ui/qml/EpisodeList.qml
Normal file
|
@ -0,0 +1,79 @@
|
|||
|
||||
import Qt 4.7
|
||||
|
||||
Rectangle {
|
||||
id: episodeList
|
||||
property alias model: listView.model
|
||||
property alias title: headerText.text
|
||||
signal goBack
|
||||
color: 'white'
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: 'podcastList/mask.png'
|
||||
sourceSize { height: 100; width: 100 }
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: 'podcastList/noise.png'
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.topMargin: header.height
|
||||
anchors.fill: parent
|
||||
model: episodeModel
|
||||
delegate: EpisodeItem {}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: header
|
||||
width: parent.width
|
||||
source: 'episodeList/header.png'
|
||||
ShadowText {
|
||||
id: headerText
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: 20
|
||||
}
|
||||
text: "Episodes"
|
||||
font.pixelSize: 30
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: backButton
|
||||
width: backButtonImage.sourceSize.width + 40
|
||||
height: parent.height
|
||||
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: backButtonHighlight
|
||||
color: "white"
|
||||
opacity: (backButtonMouseArea.pressed)?(.5):(0)
|
||||
anchors.fill: parent
|
||||
Behavior on opacity { NumberAnimation { duration: 500 } }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backButtonImage
|
||||
anchors.centerIn: parent
|
||||
source: 'episodeList/back.png'
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backButtonMouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: episodeList.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
import Qt 4.7
|
||||
|
||||
Image {
|
||||
signal podcastSelected(variant podcast)
|
||||
signal podcastContextMenu(variant podcast)
|
||||
|
||||
id: podcastItem
|
||||
source: 'podcastList/bg.png'
|
||||
width: parent.width
|
||||
|
@ -96,10 +99,10 @@ Image {
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: highlight.opacity = .2
|
||||
onClicked: descriptionText.text = "clicked"
|
||||
onClicked: parent.podcastSelected(model.podcast)
|
||||
onReleased: highlight.opacity = 0
|
||||
onCanceled: highlight.opacity = 0
|
||||
onPressAndHold: highlight.opacity = .8
|
||||
onPressAndHold: parent.podcastContextMenu(model.podcast)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,15 @@
|
|||
import Qt 4.7
|
||||
|
||||
Rectangle {
|
||||
signal podcastSelected(variant podcast)
|
||||
signal podcastContextMenu(variant podcast)
|
||||
signal action(string action)
|
||||
|
||||
id: rectangle
|
||||
color: "white"
|
||||
|
||||
property alias model: listView.model
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: 'podcastList/mask.png'
|
||||
|
@ -18,12 +24,15 @@ Rectangle {
|
|||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: (toolbarLandscape.opacity>0)?(toolbarLandscape.width+toolbarLandscape.anchors.rightMargin):(0)
|
||||
anchors.bottomMargin: (toolbar.opacity>0)?(toolbar.height+toolbar.anchors.bottomMargin):(0)
|
||||
|
||||
delegate: PodcastItem {}
|
||||
model: podcastModel
|
||||
delegate: PodcastItem {
|
||||
onPodcastSelected: rectangle.podcastSelected(podcast)
|
||||
onPodcastContextMenu: rectangle.podcastContextMenu(podcast)
|
||||
}
|
||||
snapMode: ListView.SnapToItem
|
||||
}
|
||||
|
||||
|
@ -60,7 +69,7 @@ Rectangle {
|
|||
anchors.bottomMargin: -height+height*opacity
|
||||
Behavior on anchors.bottomMargin { NumberAnimation { duration: 300 } }
|
||||
|
||||
ToolbarButton { source: 'podcastList/tb_refresh.png'; onClicked: rectangle.color = "#faa" }
|
||||
ToolbarButton { source: 'podcastList/tb_refresh.png'; onClicked: rectangle.action('refresh') }
|
||||
ToolbarButton { source: 'podcastList/tb_add.png'; onClicked: rectangle.color = "#afa" }
|
||||
ToolbarButton { source: 'podcastList/tb_search.png'; onClicked: rectangle.color = "#aaf" }
|
||||
}
|
BIN
data/ui/qml/episodeList/audio.png
Normal file
BIN
data/ui/qml/episodeList/audio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 531 B |
BIN
data/ui/qml/episodeList/back.png
Normal file
BIN
data/ui/qml/episodeList/back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
data/ui/qml/episodeList/bg.png
Normal file
BIN
data/ui/qml/episodeList/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
data/ui/qml/episodeList/download.png
Normal file
BIN
data/ui/qml/episodeList/download.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 647 B |
BIN
data/ui/qml/episodeList/header.png
Normal file
BIN
data/ui/qml/episodeList/header.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
data/ui/qml/episodeList/video.png
Normal file
BIN
data/ui/qml/episodeList/video.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 B |
41
data/ui/qml/main.qml
Normal file
41
data/ui/qml/main.qml
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
import Qt 4.7
|
||||
|
||||
Item {
|
||||
|
||||
function showEpisodes() {
|
||||
podcastList.opacity = 0
|
||||
episodeList.opacity = 1
|
||||
}
|
||||
|
||||
function showPodcasts() {
|
||||
podcastList.opacity = 1
|
||||
episodeList.opacity = 0
|
||||
}
|
||||
|
||||
PodcastList {
|
||||
id: podcastList
|
||||
model: podcastModel
|
||||
opacity: 1
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onPodcastSelected: { /*FIXME UGLY*/ episodeList.title = podcast.qtitle; controller.podcastSelected(podcast) }
|
||||
onPodcastContextMenu: controller.podcastContextMenu(podcast)
|
||||
onAction: controller.action(action)
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: 500 } }
|
||||
}
|
||||
|
||||
EpisodeList {
|
||||
id: episodeList
|
||||
model: episodeModel
|
||||
opacity: 0
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onGoBack: parent.showPodcasts()
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: 500 } }
|
||||
}
|
||||
}
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
from PySide.QtCore import *
|
||||
|
||||
import gpodder
|
||||
|
||||
from gpodder import model
|
||||
from gpodder import util
|
||||
|
||||
|
@ -28,6 +30,29 @@ class QEpisode(QObject, model.PodcastEpisode):
|
|||
QObject.__init__(self)
|
||||
model.PodcastEpisode.__init__(self, *args, **kwargs)
|
||||
|
||||
changed = Signal()
|
||||
|
||||
def _title(self):
|
||||
return self.title.decode('utf-8')
|
||||
|
||||
qtitle = Property(unicode, _title, notify=changed)
|
||||
|
||||
def _filetype(self):
|
||||
return self.file_type() or 'download' # FIXME
|
||||
|
||||
qfiletype = Property(unicode, _filetype, notify=changed)
|
||||
|
||||
def _downloaded(self):
|
||||
return self.state == gpodder.STATE_DOWNLOADED
|
||||
|
||||
qdownloaded = Property(bool, _downloaded, notify=changed)
|
||||
|
||||
def _new(self):
|
||||
return self.is_new
|
||||
|
||||
qnew = Property(bool, _new, notify=changed)
|
||||
|
||||
|
||||
class QPodcast(QObject, model.PodcastChannel):
|
||||
EpisodeClass = QEpisode
|
||||
|
||||
|
|
|
@ -15,14 +15,41 @@ from gpodder import dbsqlite
|
|||
from gpodder import config
|
||||
from gpodder import util
|
||||
|
||||
class gPodderListModel(QAbstractListModel):
|
||||
COLUMNS = ['podcast',]
|
||||
class Controller(QObject):
|
||||
def __init__(self, root):
|
||||
QObject.__init__(self)
|
||||
self.root = root
|
||||
|
||||
def __init__(self, objects):
|
||||
@Slot(QObject)
|
||||
def podcastSelected(self, podcast):
|
||||
print 'selected:', podcast.qtitle
|
||||
self.root.select_podcast(podcast)
|
||||
|
||||
@Slot(QObject)
|
||||
def podcastContextMenu(self, podcast):
|
||||
print 'context menu:', podcast.qtitle
|
||||
|
||||
@Slot(str)
|
||||
def action(self, action):
|
||||
print 'action requested:', action
|
||||
if action == 'refresh':
|
||||
self.root.reload_podcasts()
|
||||
|
||||
|
||||
class gPodderListModel(QAbstractListModel):
|
||||
COLUMNS = ['object',]
|
||||
|
||||
def __init__(self, objects=None):
|
||||
QAbstractListModel.__init__(self)
|
||||
if objects is None:
|
||||
objects = []
|
||||
self._objects = objects
|
||||
self.setRoleNames(dict(enumerate(self.COLUMNS)))
|
||||
|
||||
def set_objects(self, objects):
|
||||
self._objects = objects
|
||||
self.reset()
|
||||
|
||||
def get_object(self, index):
|
||||
return self._objects[index.row()]
|
||||
|
||||
|
@ -30,22 +57,15 @@ class gPodderListModel(QAbstractListModel):
|
|||
return len(self._objects)
|
||||
|
||||
def data(self, index, role):
|
||||
if index.isValid() and role == self.COLUMNS.index('podcast'):
|
||||
if index.isValid() and role == 0:
|
||||
return self.get_object(index)
|
||||
return None
|
||||
|
||||
class gPodderListView(QListView):
|
||||
def __init__(self, on_item_selected):
|
||||
QListView.__init__(self)
|
||||
self.setProperty('FingerScrollable', True)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self._on_item_selected = on_item_selected
|
||||
QObject.connect(self, SIGNAL('activated(QModelIndex)'), self._row_cb)
|
||||
class gPodderPodcastModel(gPodderListModel):
|
||||
COLUMNS = ['podcast',]
|
||||
|
||||
def _row_cb(self, index):
|
||||
if index.isValid():
|
||||
model = self.model()
|
||||
self._on_item_selected(model.get_object(index))
|
||||
class gPodderEpisodeModel(gPodderListModel):
|
||||
COLUMNS = ['episode',]
|
||||
|
||||
def QML(filename):
|
||||
for folder in gpodder.ui_folders:
|
||||
|
@ -56,16 +76,23 @@ def QML(filename):
|
|||
class qtPodder(QApplication):
|
||||
def __init__(self, args, config, db):
|
||||
QApplication.__init__(self, args)
|
||||
self._config = config
|
||||
self._db = db
|
||||
|
||||
podcasts = model.Model.get_podcasts(db)
|
||||
|
||||
self.controller = Controller(self)
|
||||
|
||||
self.podcast_list = QDeclarativeView()
|
||||
self.podcast_list.setResizeMode(QDeclarativeView.SizeRootObjectToView)
|
||||
|
||||
rc = self.podcast_list.rootContext()
|
||||
self.podcast_model = gPodderListModel(podcasts)
|
||||
self.podcast_model = gPodderPodcastModel(podcasts)
|
||||
self.episode_model = gPodderEpisodeModel()
|
||||
rc.setContextProperty('podcastModel', self.podcast_model)
|
||||
self.podcast_list.setSource(QML('podcastList.qml'))
|
||||
rc.setContextProperty('episodeModel', self.episode_model)
|
||||
rc.setContextProperty('controller', self.controller)
|
||||
self.podcast_list.setSource(QML('main.qml'))
|
||||
|
||||
self.podcast_window = QMainWindow()
|
||||
if gpodder.ui.fremantle:
|
||||
|
@ -75,25 +102,12 @@ class qtPodder(QApplication):
|
|||
self.podcast_window.resize(800, 480)
|
||||
self.podcast_window.show()
|
||||
|
||||
def on_podcast_selected(self, podcast):
|
||||
self.episode_list = gPodderListView(self.on_episode_selected)
|
||||
self.episode_list.setModel(gPodderListModel(podcast.get_all_episodes()))
|
||||
|
||||
self.episode_window = QMainWindow(self.podcast_window)
|
||||
window_title = u'Episodes in %s' % podcast.title.decode('utf-8')
|
||||
self.episode_window.setWindowTitle(window_title)
|
||||
self.episode_window.setCentralWidget(self.episode_list)
|
||||
self.episode_window.show()
|
||||
|
||||
def on_episode_selected(self, episode):
|
||||
if episode.was_downloaded(and_exists=True):
|
||||
util.gui_open(episode.local_filename(create=False))
|
||||
else:
|
||||
dialog = QMessageBox()
|
||||
dialog.setWindowTitle(episode.title.decode('utf-8'))
|
||||
dialog.setText('Episode not yet downloaded')
|
||||
dialog.exec_()
|
||||
def reload_podcasts(self):
|
||||
self.podcast_model.set_objects(model.Model.get_podcasts(self._db))
|
||||
|
||||
def select_podcast(self, podcast):
|
||||
self.episode_model.set_objects(podcast.get_all_episodes())
|
||||
self.podcast_list.rootObject().showEpisodes()
|
||||
|
||||
def main():
|
||||
cfg = config.Config(gpodder.config_file)
|
||||
|
|
Loading…
Reference in a new issue