]> git.sur5r.net Git - minitube/commitdiff
Categories, region selection, refactoring
authorFlavio <flavio@odisseo.local>
Mon, 7 Jan 2013 16:39:51 +0000 (17:39 +0100)
committerFlavio <flavio@odisseo.local>
Mon, 7 Jan 2013 16:39:51 +0000 (17:39 +0100)
309 files changed:
CHANGES
flags/aa.png [new file with mode: 0644]
flags/ad.png [new file with mode: 0644]
flags/ae.png [new file with mode: 0644]
flags/af.png [new file with mode: 0644]
flags/ag.png [new file with mode: 0644]
flags/ai.png [new file with mode: 0644]
flags/al.png [new file with mode: 0644]
flags/am.png [new file with mode: 0644]
flags/ao.png [new file with mode: 0644]
flags/aq.png [new file with mode: 0644]
flags/ar.png [new file with mode: 0644]
flags/as.png [new file with mode: 0644]
flags/at.png [new file with mode: 0644]
flags/au.png [new file with mode: 0644]
flags/aw.png [new file with mode: 0644]
flags/ax.png [new file with mode: 0644]
flags/az.png [new file with mode: 0644]
flags/ba.png [new file with mode: 0644]
flags/bb.png [new file with mode: 0644]
flags/bd.png [new file with mode: 0644]
flags/be.png [new file with mode: 0644]
flags/bf.png [new file with mode: 0644]
flags/bg.png [new file with mode: 0644]
flags/bh.png [new file with mode: 0644]
flags/bi.png [new file with mode: 0644]
flags/bj.png [new file with mode: 0644]
flags/bl.png [new file with mode: 0644]
flags/bm.png [new file with mode: 0644]
flags/bn.png [new file with mode: 0644]
flags/bo.png [new file with mode: 0644]
flags/bq.png [new file with mode: 0644]
flags/br.png [new file with mode: 0644]
flags/bs.png [new file with mode: 0644]
flags/bt.png [new file with mode: 0644]
flags/bv.png [new file with mode: 0644]
flags/bw.png [new file with mode: 0644]
flags/by.png [new file with mode: 0644]
flags/bz.png [new file with mode: 0644]
flags/ca.png [new file with mode: 0644]
flags/cc.png [new file with mode: 0644]
flags/cd.png [new file with mode: 0644]
flags/cf.png [new file with mode: 0644]
flags/cg.png [new file with mode: 0644]
flags/ch.png [new file with mode: 0644]
flags/ci.png [new file with mode: 0644]
flags/ck.png [new file with mode: 0644]
flags/cl.png [new file with mode: 0644]
flags/cm.png [new file with mode: 0644]
flags/cn.png [new file with mode: 0644]
flags/co.png [new file with mode: 0644]
flags/cr.png [new file with mode: 0644]
flags/cu.png [new file with mode: 0644]
flags/cv.png [new file with mode: 0644]
flags/cw.png [new file with mode: 0644]
flags/cx.png [new file with mode: 0644]
flags/cy.png [new file with mode: 0644]
flags/cz.png [new file with mode: 0644]
flags/de.png [new file with mode: 0644]
flags/dj.png [new file with mode: 0644]
flags/dk.png [new file with mode: 0644]
flags/dm.png [new file with mode: 0644]
flags/do.png [new file with mode: 0644]
flags/dz.png [new file with mode: 0644]
flags/ec.png [new file with mode: 0644]
flags/ee.png [new file with mode: 0644]
flags/eg.png [new file with mode: 0644]
flags/eh.png [new file with mode: 0644]
flags/eo.png [new file with mode: 0644]
flags/er.png [new file with mode: 0644]
flags/es.png [new file with mode: 0644]
flags/et.png [new file with mode: 0644]
flags/fi.png [new file with mode: 0644]
flags/fj.png [new file with mode: 0644]
flags/fk.png [new file with mode: 0644]
flags/fm.png [new file with mode: 0644]
flags/fo.png [new file with mode: 0644]
flags/fr.png [new file with mode: 0644]
flags/ga.png [new file with mode: 0644]
flags/gb.png [new file with mode: 0644]
flags/gd.png [new file with mode: 0644]
flags/ge.png [new file with mode: 0644]
flags/gf.png [new file with mode: 0644]
flags/gg.png [new file with mode: 0644]
flags/gh.png [new file with mode: 0644]
flags/gi.png [new file with mode: 0644]
flags/gl.png [new file with mode: 0644]
flags/gm.png [new file with mode: 0644]
flags/gn.png [new file with mode: 0644]
flags/gp.png [new file with mode: 0644]
flags/gq.png [new file with mode: 0644]
flags/gr.png [new file with mode: 0644]
flags/gs.png [new file with mode: 0644]
flags/gt.png [new file with mode: 0644]
flags/gu.png [new file with mode: 0644]
flags/gw.png [new file with mode: 0644]
flags/gy.png [new file with mode: 0644]
flags/hk.png [new file with mode: 0644]
flags/hm.png [new file with mode: 0644]
flags/hn.png [new file with mode: 0644]
flags/hr.png [new file with mode: 0644]
flags/ht.png [new file with mode: 0644]
flags/hu.png [new file with mode: 0644]
flags/id.png [new file with mode: 0644]
flags/ie.png [new file with mode: 0644]
flags/il.png [new file with mode: 0644]
flags/im.png [new file with mode: 0644]
flags/in.png [new file with mode: 0644]
flags/io.png [new file with mode: 0644]
flags/iq.png [new file with mode: 0644]
flags/ir.png [new file with mode: 0644]
flags/is.png [new file with mode: 0644]
flags/it.png [new file with mode: 0644]
flags/je.png [new file with mode: 0644]
flags/jm.png [new file with mode: 0644]
flags/jo.png [new file with mode: 0644]
flags/jp.png [new file with mode: 0644]
flags/ke.png [new file with mode: 0644]
flags/kg.png [new file with mode: 0644]
flags/kh.png [new file with mode: 0644]
flags/ki.png [new file with mode: 0644]
flags/km.png [new file with mode: 0644]
flags/kn.png [new file with mode: 0644]
flags/kp.png [new file with mode: 0644]
flags/kr.png [new file with mode: 0644]
flags/kw.png [new file with mode: 0644]
flags/ky.png [new file with mode: 0644]
flags/kz.png [new file with mode: 0644]
flags/la.png [new file with mode: 0644]
flags/lb.png [new file with mode: 0644]
flags/lc.png [new file with mode: 0644]
flags/li.png [new file with mode: 0644]
flags/lk.png [new file with mode: 0644]
flags/lr.png [new file with mode: 0644]
flags/ls.png [new file with mode: 0644]
flags/lt.png [new file with mode: 0644]
flags/lu.png [new file with mode: 0644]
flags/lv.png [new file with mode: 0644]
flags/ly.png [new file with mode: 0644]
flags/ma.png [new file with mode: 0644]
flags/mc.png [new file with mode: 0644]
flags/md.png [new file with mode: 0644]
flags/me.png [new file with mode: 0644]
flags/mf.png [new file with mode: 0644]
flags/mg.png [new file with mode: 0644]
flags/mh.png [new file with mode: 0644]
flags/mk.png [new file with mode: 0644]
flags/ml.png [new file with mode: 0644]
flags/mm.png [new file with mode: 0644]
flags/mn.png [new file with mode: 0644]
flags/mo.png [new file with mode: 0644]
flags/mp.png [new file with mode: 0644]
flags/mq.png [new file with mode: 0644]
flags/mr.png [new file with mode: 0644]
flags/ms.png [new file with mode: 0644]
flags/mt.png [new file with mode: 0644]
flags/mu.png [new file with mode: 0644]
flags/mv.png [new file with mode: 0644]
flags/mw.png [new file with mode: 0644]
flags/mx.png [new file with mode: 0644]
flags/my.png [new file with mode: 0644]
flags/mz.png [new file with mode: 0644]
flags/na.png [new file with mode: 0644]
flags/nc.png [new file with mode: 0644]
flags/ne.png [new file with mode: 0644]
flags/nf.png [new file with mode: 0644]
flags/ng.png [new file with mode: 0644]
flags/ni.png [new file with mode: 0644]
flags/nl.png [new file with mode: 0644]
flags/no.png [new file with mode: 0644]
flags/np.png [new file with mode: 0644]
flags/nr.png [new file with mode: 0644]
flags/nu.png [new file with mode: 0644]
flags/nz.png [new file with mode: 0644]
flags/om.png [new file with mode: 0644]
flags/pa.png [new file with mode: 0644]
flags/pe.png [new file with mode: 0644]
flags/pf.png [new file with mode: 0644]
flags/pg.png [new file with mode: 0644]
flags/ph.png [new file with mode: 0644]
flags/pk.png [new file with mode: 0644]
flags/pl.png [new file with mode: 0644]
flags/pm.png [new file with mode: 0644]
flags/pn.png [new file with mode: 0644]
flags/pr.png [new file with mode: 0644]
flags/ps.png [new file with mode: 0644]
flags/pt.png [new file with mode: 0644]
flags/pw.png [new file with mode: 0644]
flags/py.png [new file with mode: 0644]
flags/qa.png [new file with mode: 0644]
flags/re.png [new file with mode: 0644]
flags/ro.png [new file with mode: 0644]
flags/rs.png [new file with mode: 0644]
flags/ru.png [new file with mode: 0644]
flags/rw.png [new file with mode: 0644]
flags/sa.png [new file with mode: 0644]
flags/sb.png [new file with mode: 0644]
flags/sc.png [new file with mode: 0644]
flags/sd.png [new file with mode: 0644]
flags/se.png [new file with mode: 0644]
flags/sg.png [new file with mode: 0644]
flags/sh.png [new file with mode: 0644]
flags/si.png [new file with mode: 0644]
flags/sj.png [new file with mode: 0644]
flags/sk.png [new file with mode: 0644]
flags/sl.png [new file with mode: 0644]
flags/sm.png [new file with mode: 0644]
flags/sn.png [new file with mode: 0644]
flags/so.png [new file with mode: 0644]
flags/sr.png [new file with mode: 0644]
flags/ss.png [new file with mode: 0644]
flags/st.png [new file with mode: 0644]
flags/sv.png [new file with mode: 0644]
flags/sx.png [new file with mode: 0644]
flags/sy.png [new file with mode: 0644]
flags/sz.png [new file with mode: 0644]
flags/tc.png [new file with mode: 0644]
flags/td.png [new file with mode: 0644]
flags/tf.png [new file with mode: 0644]
flags/tg.png [new file with mode: 0644]
flags/th.png [new file with mode: 0644]
flags/tj.png [new file with mode: 0644]
flags/tk.png [new file with mode: 0644]
flags/tl.png [new file with mode: 0644]
flags/tm.png [new file with mode: 0644]
flags/tn.png [new file with mode: 0644]
flags/to.png [new file with mode: 0644]
flags/tr.png [new file with mode: 0644]
flags/tt.png [new file with mode: 0644]
flags/tv.png [new file with mode: 0644]
flags/tw.png [new file with mode: 0644]
flags/tz.png [new file with mode: 0644]
flags/ua.png [new file with mode: 0644]
flags/ug.png [new file with mode: 0644]
flags/um.png [new file with mode: 0644]
flags/us.png [new file with mode: 0644]
flags/uy.png [new file with mode: 0644]
flags/uz.png [new file with mode: 0644]
flags/va.png [new file with mode: 0644]
flags/vc.png [new file with mode: 0644]
flags/ve.png [new file with mode: 0644]
flags/vg.png [new file with mode: 0644]
flags/vi.png [new file with mode: 0644]
flags/vn.png [new file with mode: 0644]
flags/vu.png [new file with mode: 0644]
flags/wf.png [new file with mode: 0644]
flags/ws.png [new file with mode: 0644]
flags/ye.png [new file with mode: 0644]
flags/yt.png [new file with mode: 0644]
flags/za.png [new file with mode: 0644]
flags/zm.png [new file with mode: 0644]
flags/zw.png [new file with mode: 0644]
minitube.pro
resources.qrc
src/downloadmodel.cpp
src/downloadview.cpp
src/homeview.cpp [new file with mode: 0644]
src/homeview.h [new file with mode: 0644]
src/main.cpp
src/mainwindow.cpp
src/mainwindow.h
src/mediaview.cpp
src/mediaview.h
src/playlistitemdelegate.cpp
src/playlistitemdelegate.h
src/playlistmodel.cpp
src/playlistmodel.h
src/playlistsuggest.cpp [new file with mode: 0644]
src/playlistsuggest.h [new file with mode: 0644]
src/playlistview.cpp
src/playlistview.h
src/regionsview.cpp [new file with mode: 0644]
src/regionsview.h [new file with mode: 0644]
src/searchparams.cpp
src/searchparams.h
src/searchview.cpp
src/searchview.h
src/segmentedcontrol.cpp
src/sidebarheader.cpp [new file with mode: 0644]
src/sidebarheader.h [new file with mode: 0644]
src/sidebarwidget.cpp
src/sidebarwidget.h
src/standardfeedsview.cpp [new file with mode: 0644]
src/standardfeedsview.h [new file with mode: 0644]
src/userview.cpp [new file with mode: 0644]
src/userview.h [new file with mode: 0644]
src/video.cpp
src/video.h
src/videoareawidget.cpp
src/videoareawidget.h
src/videosource.cpp [new file with mode: 0644]
src/videosource.h [new file with mode: 0644]
src/videosourcewidget.cpp [new file with mode: 0644]
src/videosourcewidget.h [new file with mode: 0644]
src/ytcategories.cpp [new file with mode: 0644]
src/ytcategories.h [new file with mode: 0644]
src/ytfeedreader.cpp
src/ytfeedreader.h
src/ytregions.cpp [new file with mode: 0644]
src/ytregions.h [new file with mode: 0644]
src/ytsearch.cpp [new file with mode: 0644]
src/ytsearch.h [new file with mode: 0644]
src/ytsinglevideosource.cpp [new file with mode: 0644]
src/ytsinglevideosource.h [new file with mode: 0644]
src/ytstandardfeed.cpp [new file with mode: 0644]
src/ytstandardfeed.h [new file with mode: 0644]
src/ytsuggester.cpp
src/ytsuggester.h
style.css [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 6be6d803b7f32ba1dac3a0992f574aaecadebcdf..a3127f831939d65ccc1f12d8d1d0016df7bdd2e9 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,16 @@
-2.0
+2.0 - January ?? 2013
+- YouTube categories and "standard feeds": "Most Popular", "Featured", etc
+- Country selection for YouTube categories and feeds
 - Autoupdate on Mac and Windows
+- Related videos are now appended to the video when pasting a YouTube link
+- "Show 10 More" with a single click
+- Play video in the playlist with a single click on its thumbnail
+- OS X Mountain Lion notifications on video start
+- Fixed some YouTube links not working when pasted in the searchbox
+- Fixed playlist drag'n'drop
+- Fixed system language settings detection
 - Fixed clicking on channel names not working in some cases
+- Fixed incorrect number of downloads in status bar
 
 1.9 - September 27, 2012
 - Adapted to YouTube changes
diff --git a/flags/aa.png b/flags/aa.png
new file mode 100644 (file)
index 0000000..00de749
Binary files /dev/null and b/flags/aa.png differ
diff --git a/flags/ad.png b/flags/ad.png
new file mode 100644 (file)
index 0000000..1797beb
Binary files /dev/null and b/flags/ad.png differ
diff --git a/flags/ae.png b/flags/ae.png
new file mode 100644 (file)
index 0000000..0c4384f
Binary files /dev/null and b/flags/ae.png differ
diff --git a/flags/af.png b/flags/af.png
new file mode 100644 (file)
index 0000000..c5d6ad0
Binary files /dev/null and b/flags/af.png differ
diff --git a/flags/ag.png b/flags/ag.png
new file mode 100644 (file)
index 0000000..9cd5ef6
Binary files /dev/null and b/flags/ag.png differ
diff --git a/flags/ai.png b/flags/ai.png
new file mode 100644 (file)
index 0000000..8773497
Binary files /dev/null and b/flags/ai.png differ
diff --git a/flags/al.png b/flags/al.png
new file mode 100644 (file)
index 0000000..dc74567
Binary files /dev/null and b/flags/al.png differ
diff --git a/flags/am.png b/flags/am.png
new file mode 100644 (file)
index 0000000..ffb0468
Binary files /dev/null and b/flags/am.png differ
diff --git a/flags/ao.png b/flags/ao.png
new file mode 100644 (file)
index 0000000..bb3e36c
Binary files /dev/null and b/flags/ao.png differ
diff --git a/flags/aq.png b/flags/aq.png
new file mode 100644 (file)
index 0000000..9434a79
Binary files /dev/null and b/flags/aq.png differ
diff --git a/flags/ar.png b/flags/ar.png
new file mode 100644 (file)
index 0000000..7d8f6c1
Binary files /dev/null and b/flags/ar.png differ
diff --git a/flags/as.png b/flags/as.png
new file mode 100644 (file)
index 0000000..7dcbf45
Binary files /dev/null and b/flags/as.png differ
diff --git a/flags/at.png b/flags/at.png
new file mode 100644 (file)
index 0000000..3fe9b01
Binary files /dev/null and b/flags/at.png differ
diff --git a/flags/au.png b/flags/au.png
new file mode 100644 (file)
index 0000000..1337776
Binary files /dev/null and b/flags/au.png differ
diff --git a/flags/aw.png b/flags/aw.png
new file mode 100644 (file)
index 0000000..820da22
Binary files /dev/null and b/flags/aw.png differ
diff --git a/flags/ax.png b/flags/ax.png
new file mode 100644 (file)
index 0000000..558285c
Binary files /dev/null and b/flags/ax.png differ
diff --git a/flags/az.png b/flags/az.png
new file mode 100644 (file)
index 0000000..d3a59e9
Binary files /dev/null and b/flags/az.png differ
diff --git a/flags/ba.png b/flags/ba.png
new file mode 100644 (file)
index 0000000..8113723
Binary files /dev/null and b/flags/ba.png differ
diff --git a/flags/bb.png b/flags/bb.png
new file mode 100644 (file)
index 0000000..81bccb3
Binary files /dev/null and b/flags/bb.png differ
diff --git a/flags/bd.png b/flags/bd.png
new file mode 100644 (file)
index 0000000..984d0b1
Binary files /dev/null and b/flags/bd.png differ
diff --git a/flags/be.png b/flags/be.png
new file mode 100644 (file)
index 0000000..8db34d3
Binary files /dev/null and b/flags/be.png differ
diff --git a/flags/bf.png b/flags/bf.png
new file mode 100644 (file)
index 0000000..27a9bc0
Binary files /dev/null and b/flags/bf.png differ
diff --git a/flags/bg.png b/flags/bg.png
new file mode 100644 (file)
index 0000000..656ccae
Binary files /dev/null and b/flags/bg.png differ
diff --git a/flags/bh.png b/flags/bh.png
new file mode 100644 (file)
index 0000000..76b0abd
Binary files /dev/null and b/flags/bh.png differ
diff --git a/flags/bi.png b/flags/bi.png
new file mode 100644 (file)
index 0000000..5ee2762
Binary files /dev/null and b/flags/bi.png differ
diff --git a/flags/bj.png b/flags/bj.png
new file mode 100644 (file)
index 0000000..8eb7145
Binary files /dev/null and b/flags/bj.png differ
diff --git a/flags/bl.png b/flags/bl.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/bl.png differ
diff --git a/flags/bm.png b/flags/bm.png
new file mode 100644 (file)
index 0000000..dfb2903
Binary files /dev/null and b/flags/bm.png differ
diff --git a/flags/bn.png b/flags/bn.png
new file mode 100644 (file)
index 0000000..bb297c0
Binary files /dev/null and b/flags/bn.png differ
diff --git a/flags/bo.png b/flags/bo.png
new file mode 100644 (file)
index 0000000..0b925cc
Binary files /dev/null and b/flags/bo.png differ
diff --git a/flags/bq.png b/flags/bq.png
new file mode 100644 (file)
index 0000000..0537acb
Binary files /dev/null and b/flags/bq.png differ
diff --git a/flags/br.png b/flags/br.png
new file mode 100644 (file)
index 0000000..0388ab2
Binary files /dev/null and b/flags/br.png differ
diff --git a/flags/bs.png b/flags/bs.png
new file mode 100644 (file)
index 0000000..08d7740
Binary files /dev/null and b/flags/bs.png differ
diff --git a/flags/bt.png b/flags/bt.png
new file mode 100644 (file)
index 0000000..5dbf784
Binary files /dev/null and b/flags/bt.png differ
diff --git a/flags/bv.png b/flags/bv.png
new file mode 100644 (file)
index 0000000..efd5c47
Binary files /dev/null and b/flags/bv.png differ
diff --git a/flags/bw.png b/flags/bw.png
new file mode 100644 (file)
index 0000000..324f49d
Binary files /dev/null and b/flags/bw.png differ
diff --git a/flags/by.png b/flags/by.png
new file mode 100644 (file)
index 0000000..4af566a
Binary files /dev/null and b/flags/by.png differ
diff --git a/flags/bz.png b/flags/bz.png
new file mode 100644 (file)
index 0000000..8e03b90
Binary files /dev/null and b/flags/bz.png differ
diff --git a/flags/ca.png b/flags/ca.png
new file mode 100644 (file)
index 0000000..41e3b9b
Binary files /dev/null and b/flags/ca.png differ
diff --git a/flags/cc.png b/flags/cc.png
new file mode 100644 (file)
index 0000000..1337776
Binary files /dev/null and b/flags/cc.png differ
diff --git a/flags/cd.png b/flags/cd.png
new file mode 100644 (file)
index 0000000..d6db89e
Binary files /dev/null and b/flags/cd.png differ
diff --git a/flags/cf.png b/flags/cf.png
new file mode 100644 (file)
index 0000000..40bc0bc
Binary files /dev/null and b/flags/cf.png differ
diff --git a/flags/cg.png b/flags/cg.png
new file mode 100644 (file)
index 0000000..73b99f9
Binary files /dev/null and b/flags/cg.png differ
diff --git a/flags/ch.png b/flags/ch.png
new file mode 100644 (file)
index 0000000..2bcf775
Binary files /dev/null and b/flags/ch.png differ
diff --git a/flags/ci.png b/flags/ci.png
new file mode 100644 (file)
index 0000000..fa83e47
Binary files /dev/null and b/flags/ci.png differ
diff --git a/flags/ck.png b/flags/ck.png
new file mode 100644 (file)
index 0000000..970a2b6
Binary files /dev/null and b/flags/ck.png differ
diff --git a/flags/cl.png b/flags/cl.png
new file mode 100644 (file)
index 0000000..b8a76b0
Binary files /dev/null and b/flags/cl.png differ
diff --git a/flags/cm.png b/flags/cm.png
new file mode 100644 (file)
index 0000000..c5b103d
Binary files /dev/null and b/flags/cm.png differ
diff --git a/flags/cn.png b/flags/cn.png
new file mode 100644 (file)
index 0000000..643342a
Binary files /dev/null and b/flags/cn.png differ
diff --git a/flags/co.png b/flags/co.png
new file mode 100644 (file)
index 0000000..d8ce22f
Binary files /dev/null and b/flags/co.png differ
diff --git a/flags/cr.png b/flags/cr.png
new file mode 100644 (file)
index 0000000..203d97d
Binary files /dev/null and b/flags/cr.png differ
diff --git a/flags/cu.png b/flags/cu.png
new file mode 100644 (file)
index 0000000..df19b36
Binary files /dev/null and b/flags/cu.png differ
diff --git a/flags/cv.png b/flags/cv.png
new file mode 100644 (file)
index 0000000..5d3738b
Binary files /dev/null and b/flags/cv.png differ
diff --git a/flags/cw.png b/flags/cw.png
new file mode 100644 (file)
index 0000000..8df3897
Binary files /dev/null and b/flags/cw.png differ
diff --git a/flags/cx.png b/flags/cx.png
new file mode 100644 (file)
index 0000000..9d5e140
Binary files /dev/null and b/flags/cx.png differ
diff --git a/flags/cy.png b/flags/cy.png
new file mode 100644 (file)
index 0000000..f1b75cc
Binary files /dev/null and b/flags/cy.png differ
diff --git a/flags/cz.png b/flags/cz.png
new file mode 100644 (file)
index 0000000..7045331
Binary files /dev/null and b/flags/cz.png differ
diff --git a/flags/de.png b/flags/de.png
new file mode 100644 (file)
index 0000000..6e4227c
Binary files /dev/null and b/flags/de.png differ
diff --git a/flags/dj.png b/flags/dj.png
new file mode 100644 (file)
index 0000000..f47f94a
Binary files /dev/null and b/flags/dj.png differ
diff --git a/flags/dk.png b/flags/dk.png
new file mode 100644 (file)
index 0000000..9b4d4dd
Binary files /dev/null and b/flags/dk.png differ
diff --git a/flags/dm.png b/flags/dm.png
new file mode 100644 (file)
index 0000000..b7ca081
Binary files /dev/null and b/flags/dm.png differ
diff --git a/flags/do.png b/flags/do.png
new file mode 100644 (file)
index 0000000..28d278e
Binary files /dev/null and b/flags/do.png differ
diff --git a/flags/dz.png b/flags/dz.png
new file mode 100644 (file)
index 0000000..f7ee5a5
Binary files /dev/null and b/flags/dz.png differ
diff --git a/flags/ec.png b/flags/ec.png
new file mode 100644 (file)
index 0000000..8fadbf3
Binary files /dev/null and b/flags/ec.png differ
diff --git a/flags/ee.png b/flags/ee.png
new file mode 100644 (file)
index 0000000..b396554
Binary files /dev/null and b/flags/ee.png differ
diff --git a/flags/eg.png b/flags/eg.png
new file mode 100644 (file)
index 0000000..6a6c757
Binary files /dev/null and b/flags/eg.png differ
diff --git a/flags/eh.png b/flags/eh.png
new file mode 100644 (file)
index 0000000..cb34f16
Binary files /dev/null and b/flags/eh.png differ
diff --git a/flags/eo.png b/flags/eo.png
new file mode 100644 (file)
index 0000000..a81582a
Binary files /dev/null and b/flags/eo.png differ
diff --git a/flags/er.png b/flags/er.png
new file mode 100644 (file)
index 0000000..759a70f
Binary files /dev/null and b/flags/er.png differ
diff --git a/flags/es.png b/flags/es.png
new file mode 100644 (file)
index 0000000..e0221d0
Binary files /dev/null and b/flags/es.png differ
diff --git a/flags/et.png b/flags/et.png
new file mode 100644 (file)
index 0000000..f9b07e1
Binary files /dev/null and b/flags/et.png differ
diff --git a/flags/fi.png b/flags/fi.png
new file mode 100644 (file)
index 0000000..fa748e0
Binary files /dev/null and b/flags/fi.png differ
diff --git a/flags/fj.png b/flags/fj.png
new file mode 100644 (file)
index 0000000..e2374ef
Binary files /dev/null and b/flags/fj.png differ
diff --git a/flags/fk.png b/flags/fk.png
new file mode 100644 (file)
index 0000000..880b9fe
Binary files /dev/null and b/flags/fk.png differ
diff --git a/flags/fm.png b/flags/fm.png
new file mode 100644 (file)
index 0000000..05d0669
Binary files /dev/null and b/flags/fm.png differ
diff --git a/flags/fo.png b/flags/fo.png
new file mode 100644 (file)
index 0000000..c32504c
Binary files /dev/null and b/flags/fo.png differ
diff --git a/flags/fr.png b/flags/fr.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/fr.png differ
diff --git a/flags/ga.png b/flags/ga.png
new file mode 100644 (file)
index 0000000..448829f
Binary files /dev/null and b/flags/ga.png differ
diff --git a/flags/gb.png b/flags/gb.png
new file mode 100644 (file)
index 0000000..7792b6f
Binary files /dev/null and b/flags/gb.png differ
diff --git a/flags/gd.png b/flags/gd.png
new file mode 100644 (file)
index 0000000..2907cdd
Binary files /dev/null and b/flags/gd.png differ
diff --git a/flags/ge.png b/flags/ge.png
new file mode 100644 (file)
index 0000000..3e46cb6
Binary files /dev/null and b/flags/ge.png differ
diff --git a/flags/gf.png b/flags/gf.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/gf.png differ
diff --git a/flags/gg.png b/flags/gg.png
new file mode 100644 (file)
index 0000000..8d7dff0
Binary files /dev/null and b/flags/gg.png differ
diff --git a/flags/gh.png b/flags/gh.png
new file mode 100644 (file)
index 0000000..1f7e7a1
Binary files /dev/null and b/flags/gh.png differ
diff --git a/flags/gi.png b/flags/gi.png
new file mode 100644 (file)
index 0000000..dcfd125
Binary files /dev/null and b/flags/gi.png differ
diff --git a/flags/gl.png b/flags/gl.png
new file mode 100644 (file)
index 0000000..fcfd3f1
Binary files /dev/null and b/flags/gl.png differ
diff --git a/flags/gm.png b/flags/gm.png
new file mode 100644 (file)
index 0000000..e6036b5
Binary files /dev/null and b/flags/gm.png differ
diff --git a/flags/gn.png b/flags/gn.png
new file mode 100644 (file)
index 0000000..dc9f0a3
Binary files /dev/null and b/flags/gn.png differ
diff --git a/flags/gp.png b/flags/gp.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/gp.png differ
diff --git a/flags/gq.png b/flags/gq.png
new file mode 100644 (file)
index 0000000..027f740
Binary files /dev/null and b/flags/gq.png differ
diff --git a/flags/gr.png b/flags/gr.png
new file mode 100644 (file)
index 0000000..08e7682
Binary files /dev/null and b/flags/gr.png differ
diff --git a/flags/gs.png b/flags/gs.png
new file mode 100644 (file)
index 0000000..9ecf002
Binary files /dev/null and b/flags/gs.png differ
diff --git a/flags/gt.png b/flags/gt.png
new file mode 100644 (file)
index 0000000..8578339
Binary files /dev/null and b/flags/gt.png differ
diff --git a/flags/gu.png b/flags/gu.png
new file mode 100644 (file)
index 0000000..03a2240
Binary files /dev/null and b/flags/gu.png differ
diff --git a/flags/gw.png b/flags/gw.png
new file mode 100644 (file)
index 0000000..8eab2cf
Binary files /dev/null and b/flags/gw.png differ
diff --git a/flags/gy.png b/flags/gy.png
new file mode 100644 (file)
index 0000000..8c2a856
Binary files /dev/null and b/flags/gy.png differ
diff --git a/flags/hk.png b/flags/hk.png
new file mode 100644 (file)
index 0000000..8c5fd7a
Binary files /dev/null and b/flags/hk.png differ
diff --git a/flags/hm.png b/flags/hm.png
new file mode 100644 (file)
index 0000000..369a261
Binary files /dev/null and b/flags/hm.png differ
diff --git a/flags/hn.png b/flags/hn.png
new file mode 100644 (file)
index 0000000..837a4c5
Binary files /dev/null and b/flags/hn.png differ
diff --git a/flags/hr.png b/flags/hr.png
new file mode 100644 (file)
index 0000000..7d44171
Binary files /dev/null and b/flags/hr.png differ
diff --git a/flags/ht.png b/flags/ht.png
new file mode 100644 (file)
index 0000000..e899329
Binary files /dev/null and b/flags/ht.png differ
diff --git a/flags/hu.png b/flags/hu.png
new file mode 100644 (file)
index 0000000..3409688
Binary files /dev/null and b/flags/hu.png differ
diff --git a/flags/id.png b/flags/id.png
new file mode 100644 (file)
index 0000000..8121a6c
Binary files /dev/null and b/flags/id.png differ
diff --git a/flags/ie.png b/flags/ie.png
new file mode 100644 (file)
index 0000000..be36b16
Binary files /dev/null and b/flags/ie.png differ
diff --git a/flags/il.png b/flags/il.png
new file mode 100644 (file)
index 0000000..2559b9a
Binary files /dev/null and b/flags/il.png differ
diff --git a/flags/im.png b/flags/im.png
new file mode 100644 (file)
index 0000000..4b7eff7
Binary files /dev/null and b/flags/im.png differ
diff --git a/flags/in.png b/flags/in.png
new file mode 100644 (file)
index 0000000..f1bbf5b
Binary files /dev/null and b/flags/in.png differ
diff --git a/flags/io.png b/flags/io.png
new file mode 100644 (file)
index 0000000..a22b14f
Binary files /dev/null and b/flags/io.png differ
diff --git a/flags/iq.png b/flags/iq.png
new file mode 100644 (file)
index 0000000..c567515
Binary files /dev/null and b/flags/iq.png differ
diff --git a/flags/ir.png b/flags/ir.png
new file mode 100644 (file)
index 0000000..e9242fb
Binary files /dev/null and b/flags/ir.png differ
diff --git a/flags/is.png b/flags/is.png
new file mode 100644 (file)
index 0000000..00c2758
Binary files /dev/null and b/flags/is.png differ
diff --git a/flags/it.png b/flags/it.png
new file mode 100644 (file)
index 0000000..8ac6699
Binary files /dev/null and b/flags/it.png differ
diff --git a/flags/je.png b/flags/je.png
new file mode 100644 (file)
index 0000000..afca28e
Binary files /dev/null and b/flags/je.png differ
diff --git a/flags/jm.png b/flags/jm.png
new file mode 100644 (file)
index 0000000..42923df
Binary files /dev/null and b/flags/jm.png differ
diff --git a/flags/jo.png b/flags/jo.png
new file mode 100644 (file)
index 0000000..65deddc
Binary files /dev/null and b/flags/jo.png differ
diff --git a/flags/jp.png b/flags/jp.png
new file mode 100644 (file)
index 0000000..478fd9f
Binary files /dev/null and b/flags/jp.png differ
diff --git a/flags/ke.png b/flags/ke.png
new file mode 100644 (file)
index 0000000..580e255
Binary files /dev/null and b/flags/ke.png differ
diff --git a/flags/kg.png b/flags/kg.png
new file mode 100644 (file)
index 0000000..64fd7a3
Binary files /dev/null and b/flags/kg.png differ
diff --git a/flags/kh.png b/flags/kh.png
new file mode 100644 (file)
index 0000000..5367f59
Binary files /dev/null and b/flags/kh.png differ
diff --git a/flags/ki.png b/flags/ki.png
new file mode 100644 (file)
index 0000000..74ebc68
Binary files /dev/null and b/flags/ki.png differ
diff --git a/flags/km.png b/flags/km.png
new file mode 100644 (file)
index 0000000..3585c04
Binary files /dev/null and b/flags/km.png differ
diff --git a/flags/kn.png b/flags/kn.png
new file mode 100644 (file)
index 0000000..7f0625e
Binary files /dev/null and b/flags/kn.png differ
diff --git a/flags/kp.png b/flags/kp.png
new file mode 100644 (file)
index 0000000..7abcf8e
Binary files /dev/null and b/flags/kp.png differ
diff --git a/flags/kr.png b/flags/kr.png
new file mode 100644 (file)
index 0000000..590b981
Binary files /dev/null and b/flags/kr.png differ
diff --git a/flags/kw.png b/flags/kw.png
new file mode 100644 (file)
index 0000000..973cbe1
Binary files /dev/null and b/flags/kw.png differ
diff --git a/flags/ky.png b/flags/ky.png
new file mode 100644 (file)
index 0000000..339eb9a
Binary files /dev/null and b/flags/ky.png differ
diff --git a/flags/kz.png b/flags/kz.png
new file mode 100644 (file)
index 0000000..89ff910
Binary files /dev/null and b/flags/kz.png differ
diff --git a/flags/la.png b/flags/la.png
new file mode 100644 (file)
index 0000000..d7f3c1d
Binary files /dev/null and b/flags/la.png differ
diff --git a/flags/lb.png b/flags/lb.png
new file mode 100644 (file)
index 0000000..86df7b5
Binary files /dev/null and b/flags/lb.png differ
diff --git a/flags/lc.png b/flags/lc.png
new file mode 100644 (file)
index 0000000..d522ed7
Binary files /dev/null and b/flags/lc.png differ
diff --git a/flags/li.png b/flags/li.png
new file mode 100644 (file)
index 0000000..9c9d485
Binary files /dev/null and b/flags/li.png differ
diff --git a/flags/lk.png b/flags/lk.png
new file mode 100644 (file)
index 0000000..78f8768
Binary files /dev/null and b/flags/lk.png differ
diff --git a/flags/lr.png b/flags/lr.png
new file mode 100644 (file)
index 0000000..6c564bc
Binary files /dev/null and b/flags/lr.png differ
diff --git a/flags/ls.png b/flags/ls.png
new file mode 100644 (file)
index 0000000..5c5336b
Binary files /dev/null and b/flags/ls.png differ
diff --git a/flags/lt.png b/flags/lt.png
new file mode 100644 (file)
index 0000000..8a359a5
Binary files /dev/null and b/flags/lt.png differ
diff --git a/flags/lu.png b/flags/lu.png
new file mode 100644 (file)
index 0000000..8cef647
Binary files /dev/null and b/flags/lu.png differ
diff --git a/flags/lv.png b/flags/lv.png
new file mode 100644 (file)
index 0000000..6398693
Binary files /dev/null and b/flags/lv.png differ
diff --git a/flags/ly.png b/flags/ly.png
new file mode 100644 (file)
index 0000000..266a909
Binary files /dev/null and b/flags/ly.png differ
diff --git a/flags/ma.png b/flags/ma.png
new file mode 100644 (file)
index 0000000..bb29b17
Binary files /dev/null and b/flags/ma.png differ
diff --git a/flags/mc.png b/flags/mc.png
new file mode 100644 (file)
index 0000000..7d5bccb
Binary files /dev/null and b/flags/mc.png differ
diff --git a/flags/md.png b/flags/md.png
new file mode 100644 (file)
index 0000000..5045a30
Binary files /dev/null and b/flags/md.png differ
diff --git a/flags/me.png b/flags/me.png
new file mode 100644 (file)
index 0000000..426fbf5
Binary files /dev/null and b/flags/me.png differ
diff --git a/flags/mf.png b/flags/mf.png
new file mode 100644 (file)
index 0000000..691826d
Binary files /dev/null and b/flags/mf.png differ
diff --git a/flags/mg.png b/flags/mg.png
new file mode 100644 (file)
index 0000000..83510ff
Binary files /dev/null and b/flags/mg.png differ
diff --git a/flags/mh.png b/flags/mh.png
new file mode 100644 (file)
index 0000000..c5bbf2d
Binary files /dev/null and b/flags/mh.png differ
diff --git a/flags/mk.png b/flags/mk.png
new file mode 100644 (file)
index 0000000..223af3e
Binary files /dev/null and b/flags/mk.png differ
diff --git a/flags/ml.png b/flags/ml.png
new file mode 100644 (file)
index 0000000..ccdd589
Binary files /dev/null and b/flags/ml.png differ
diff --git a/flags/mm.png b/flags/mm.png
new file mode 100644 (file)
index 0000000..122c42b
Binary files /dev/null and b/flags/mm.png differ
diff --git a/flags/mn.png b/flags/mn.png
new file mode 100644 (file)
index 0000000..741d90e
Binary files /dev/null and b/flags/mn.png differ
diff --git a/flags/mo.png b/flags/mo.png
new file mode 100644 (file)
index 0000000..2b7762c
Binary files /dev/null and b/flags/mo.png differ
diff --git a/flags/mp.png b/flags/mp.png
new file mode 100644 (file)
index 0000000..70f164a
Binary files /dev/null and b/flags/mp.png differ
diff --git a/flags/mq.png b/flags/mq.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/mq.png differ
diff --git a/flags/mr.png b/flags/mr.png
new file mode 100644 (file)
index 0000000..2c3ddf5
Binary files /dev/null and b/flags/mr.png differ
diff --git a/flags/ms.png b/flags/ms.png
new file mode 100644 (file)
index 0000000..a493f41
Binary files /dev/null and b/flags/ms.png differ
diff --git a/flags/mt.png b/flags/mt.png
new file mode 100644 (file)
index 0000000..f276dcc
Binary files /dev/null and b/flags/mt.png differ
diff --git a/flags/mu.png b/flags/mu.png
new file mode 100644 (file)
index 0000000..e85120c
Binary files /dev/null and b/flags/mu.png differ
diff --git a/flags/mv.png b/flags/mv.png
new file mode 100644 (file)
index 0000000..8922669
Binary files /dev/null and b/flags/mv.png differ
diff --git a/flags/mw.png b/flags/mw.png
new file mode 100644 (file)
index 0000000..d24b38a
Binary files /dev/null and b/flags/mw.png differ
diff --git a/flags/mx.png b/flags/mx.png
new file mode 100644 (file)
index 0000000..64bc962
Binary files /dev/null and b/flags/mx.png differ
diff --git a/flags/my.png b/flags/my.png
new file mode 100644 (file)
index 0000000..3ba4e6c
Binary files /dev/null and b/flags/my.png differ
diff --git a/flags/mz.png b/flags/mz.png
new file mode 100644 (file)
index 0000000..812613e
Binary files /dev/null and b/flags/mz.png differ
diff --git a/flags/na.png b/flags/na.png
new file mode 100644 (file)
index 0000000..e395bd8
Binary files /dev/null and b/flags/na.png differ
diff --git a/flags/nc.png b/flags/nc.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/nc.png differ
diff --git a/flags/ne.png b/flags/ne.png
new file mode 100644 (file)
index 0000000..8fba5a1
Binary files /dev/null and b/flags/ne.png differ
diff --git a/flags/nf.png b/flags/nf.png
new file mode 100644 (file)
index 0000000..37ab3a8
Binary files /dev/null and b/flags/nf.png differ
diff --git a/flags/ng.png b/flags/ng.png
new file mode 100644 (file)
index 0000000..5dd9c1a
Binary files /dev/null and b/flags/ng.png differ
diff --git a/flags/ni.png b/flags/ni.png
new file mode 100644 (file)
index 0000000..50cc637
Binary files /dev/null and b/flags/ni.png differ
diff --git a/flags/nl.png b/flags/nl.png
new file mode 100644 (file)
index 0000000..df2794e
Binary files /dev/null and b/flags/nl.png differ
diff --git a/flags/no.png b/flags/no.png
new file mode 100644 (file)
index 0000000..59138a7
Binary files /dev/null and b/flags/no.png differ
diff --git a/flags/np.png b/flags/np.png
new file mode 100644 (file)
index 0000000..37c8f29
Binary files /dev/null and b/flags/np.png differ
diff --git a/flags/nr.png b/flags/nr.png
new file mode 100644 (file)
index 0000000..d6408e2
Binary files /dev/null and b/flags/nr.png differ
diff --git a/flags/nu.png b/flags/nu.png
new file mode 100644 (file)
index 0000000..1dd6000
Binary files /dev/null and b/flags/nu.png differ
diff --git a/flags/nz.png b/flags/nz.png
new file mode 100644 (file)
index 0000000..ba5dfd1
Binary files /dev/null and b/flags/nz.png differ
diff --git a/flags/om.png b/flags/om.png
new file mode 100644 (file)
index 0000000..692229f
Binary files /dev/null and b/flags/om.png differ
diff --git a/flags/pa.png b/flags/pa.png
new file mode 100644 (file)
index 0000000..d1b7838
Binary files /dev/null and b/flags/pa.png differ
diff --git a/flags/pe.png b/flags/pe.png
new file mode 100644 (file)
index 0000000..513b986
Binary files /dev/null and b/flags/pe.png differ
diff --git a/flags/pf.png b/flags/pf.png
new file mode 100644 (file)
index 0000000..3bffe95
Binary files /dev/null and b/flags/pf.png differ
diff --git a/flags/pg.png b/flags/pg.png
new file mode 100644 (file)
index 0000000..f674483
Binary files /dev/null and b/flags/pg.png differ
diff --git a/flags/ph.png b/flags/ph.png
new file mode 100644 (file)
index 0000000..3c5c242
Binary files /dev/null and b/flags/ph.png differ
diff --git a/flags/pk.png b/flags/pk.png
new file mode 100644 (file)
index 0000000..bcc771c
Binary files /dev/null and b/flags/pk.png differ
diff --git a/flags/pl.png b/flags/pl.png
new file mode 100644 (file)
index 0000000..2ac25fe
Binary files /dev/null and b/flags/pl.png differ
diff --git a/flags/pm.png b/flags/pm.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/pm.png differ
diff --git a/flags/pn.png b/flags/pn.png
new file mode 100644 (file)
index 0000000..c88f11c
Binary files /dev/null and b/flags/pn.png differ
diff --git a/flags/pr.png b/flags/pr.png
new file mode 100644 (file)
index 0000000..3cdf8a2
Binary files /dev/null and b/flags/pr.png differ
diff --git a/flags/ps.png b/flags/ps.png
new file mode 100644 (file)
index 0000000..c0a7e9c
Binary files /dev/null and b/flags/ps.png differ
diff --git a/flags/pt.png b/flags/pt.png
new file mode 100644 (file)
index 0000000..37ff303
Binary files /dev/null and b/flags/pt.png differ
diff --git a/flags/pw.png b/flags/pw.png
new file mode 100644 (file)
index 0000000..40ca642
Binary files /dev/null and b/flags/pw.png differ
diff --git a/flags/py.png b/flags/py.png
new file mode 100644 (file)
index 0000000..4801324
Binary files /dev/null and b/flags/py.png differ
diff --git a/flags/qa.png b/flags/qa.png
new file mode 100644 (file)
index 0000000..5983296
Binary files /dev/null and b/flags/qa.png differ
diff --git a/flags/re.png b/flags/re.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/re.png differ
diff --git a/flags/ro.png b/flags/ro.png
new file mode 100644 (file)
index 0000000..ad1ffd2
Binary files /dev/null and b/flags/ro.png differ
diff --git a/flags/rs.png b/flags/rs.png
new file mode 100644 (file)
index 0000000..937a33e
Binary files /dev/null and b/flags/rs.png differ
diff --git a/flags/ru.png b/flags/ru.png
new file mode 100644 (file)
index 0000000..5575bee
Binary files /dev/null and b/flags/ru.png differ
diff --git a/flags/rw.png b/flags/rw.png
new file mode 100644 (file)
index 0000000..4189609
Binary files /dev/null and b/flags/rw.png differ
diff --git a/flags/sa.png b/flags/sa.png
new file mode 100644 (file)
index 0000000..0ac21cd
Binary files /dev/null and b/flags/sa.png differ
diff --git a/flags/sb.png b/flags/sb.png
new file mode 100644 (file)
index 0000000..cd27a52
Binary files /dev/null and b/flags/sb.png differ
diff --git a/flags/sc.png b/flags/sc.png
new file mode 100644 (file)
index 0000000..6163323
Binary files /dev/null and b/flags/sc.png differ
diff --git a/flags/sd.png b/flags/sd.png
new file mode 100644 (file)
index 0000000..3d5fdbf
Binary files /dev/null and b/flags/sd.png differ
diff --git a/flags/se.png b/flags/se.png
new file mode 100644 (file)
index 0000000..f838195
Binary files /dev/null and b/flags/se.png differ
diff --git a/flags/sg.png b/flags/sg.png
new file mode 100644 (file)
index 0000000..3109de1
Binary files /dev/null and b/flags/sg.png differ
diff --git a/flags/sh.png b/flags/sh.png
new file mode 100644 (file)
index 0000000..41f6dd9
Binary files /dev/null and b/flags/sh.png differ
diff --git a/flags/si.png b/flags/si.png
new file mode 100644 (file)
index 0000000..442768e
Binary files /dev/null and b/flags/si.png differ
diff --git a/flags/sj.png b/flags/sj.png
new file mode 100644 (file)
index 0000000..980b2e4
Binary files /dev/null and b/flags/sj.png differ
diff --git a/flags/sk.png b/flags/sk.png
new file mode 100644 (file)
index 0000000..ed0ae19
Binary files /dev/null and b/flags/sk.png differ
diff --git a/flags/sl.png b/flags/sl.png
new file mode 100644 (file)
index 0000000..1403c70
Binary files /dev/null and b/flags/sl.png differ
diff --git a/flags/sm.png b/flags/sm.png
new file mode 100644 (file)
index 0000000..69b230c
Binary files /dev/null and b/flags/sm.png differ
diff --git a/flags/sn.png b/flags/sn.png
new file mode 100644 (file)
index 0000000..53f1dda
Binary files /dev/null and b/flags/sn.png differ
diff --git a/flags/so.png b/flags/so.png
new file mode 100644 (file)
index 0000000..3b3bcc6
Binary files /dev/null and b/flags/so.png differ
diff --git a/flags/sr.png b/flags/sr.png
new file mode 100644 (file)
index 0000000..4e55bbc
Binary files /dev/null and b/flags/sr.png differ
diff --git a/flags/ss.png b/flags/ss.png
new file mode 100644 (file)
index 0000000..f809b6d
Binary files /dev/null and b/flags/ss.png differ
diff --git a/flags/st.png b/flags/st.png
new file mode 100644 (file)
index 0000000..976f5b8
Binary files /dev/null and b/flags/st.png differ
diff --git a/flags/sv.png b/flags/sv.png
new file mode 100644 (file)
index 0000000..9e96582
Binary files /dev/null and b/flags/sv.png differ
diff --git a/flags/sx.png b/flags/sx.png
new file mode 100644 (file)
index 0000000..691826d
Binary files /dev/null and b/flags/sx.png differ
diff --git a/flags/sy.png b/flags/sy.png
new file mode 100644 (file)
index 0000000..900dc2d
Binary files /dev/null and b/flags/sy.png differ
diff --git a/flags/sz.png b/flags/sz.png
new file mode 100644 (file)
index 0000000..8153162
Binary files /dev/null and b/flags/sz.png differ
diff --git a/flags/tc.png b/flags/tc.png
new file mode 100644 (file)
index 0000000..cddd919
Binary files /dev/null and b/flags/tc.png differ
diff --git a/flags/td.png b/flags/td.png
new file mode 100644 (file)
index 0000000..4ee6a39
Binary files /dev/null and b/flags/td.png differ
diff --git a/flags/tf.png b/flags/tf.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/tf.png differ
diff --git a/flags/tg.png b/flags/tg.png
new file mode 100644 (file)
index 0000000..9d6ab4a
Binary files /dev/null and b/flags/tg.png differ
diff --git a/flags/th.png b/flags/th.png
new file mode 100644 (file)
index 0000000..1aed666
Binary files /dev/null and b/flags/th.png differ
diff --git a/flags/tj.png b/flags/tj.png
new file mode 100644 (file)
index 0000000..630cc7d
Binary files /dev/null and b/flags/tj.png differ
diff --git a/flags/tk.png b/flags/tk.png
new file mode 100644 (file)
index 0000000..0c7a257
Binary files /dev/null and b/flags/tk.png differ
diff --git a/flags/tl.png b/flags/tl.png
new file mode 100644 (file)
index 0000000..b4c043f
Binary files /dev/null and b/flags/tl.png differ
diff --git a/flags/tm.png b/flags/tm.png
new file mode 100644 (file)
index 0000000..6eea744
Binary files /dev/null and b/flags/tm.png differ
diff --git a/flags/tn.png b/flags/tn.png
new file mode 100644 (file)
index 0000000..109f393
Binary files /dev/null and b/flags/tn.png differ
diff --git a/flags/to.png b/flags/to.png
new file mode 100644 (file)
index 0000000..2b808e4
Binary files /dev/null and b/flags/to.png differ
diff --git a/flags/tr.png b/flags/tr.png
new file mode 100644 (file)
index 0000000..43aa432
Binary files /dev/null and b/flags/tr.png differ
diff --git a/flags/tt.png b/flags/tt.png
new file mode 100644 (file)
index 0000000..ad90bf1
Binary files /dev/null and b/flags/tt.png differ
diff --git a/flags/tv.png b/flags/tv.png
new file mode 100644 (file)
index 0000000..fab20c6
Binary files /dev/null and b/flags/tv.png differ
diff --git a/flags/tw.png b/flags/tw.png
new file mode 100644 (file)
index 0000000..733d370
Binary files /dev/null and b/flags/tw.png differ
diff --git a/flags/tz.png b/flags/tz.png
new file mode 100644 (file)
index 0000000..bfc0993
Binary files /dev/null and b/flags/tz.png differ
diff --git a/flags/ua.png b/flags/ua.png
new file mode 100644 (file)
index 0000000..383c127
Binary files /dev/null and b/flags/ua.png differ
diff --git a/flags/ug.png b/flags/ug.png
new file mode 100644 (file)
index 0000000..4c977f2
Binary files /dev/null and b/flags/ug.png differ
diff --git a/flags/um.png b/flags/um.png
new file mode 100644 (file)
index 0000000..9ca0bfd
Binary files /dev/null and b/flags/um.png differ
diff --git a/flags/us.png b/flags/us.png
new file mode 100644 (file)
index 0000000..9ca0bfd
Binary files /dev/null and b/flags/us.png differ
diff --git a/flags/uy.png b/flags/uy.png
new file mode 100644 (file)
index 0000000..a90b9e0
Binary files /dev/null and b/flags/uy.png differ
diff --git a/flags/uz.png b/flags/uz.png
new file mode 100644 (file)
index 0000000..d83031f
Binary files /dev/null and b/flags/uz.png differ
diff --git a/flags/va.png b/flags/va.png
new file mode 100644 (file)
index 0000000..9935a8e
Binary files /dev/null and b/flags/va.png differ
diff --git a/flags/vc.png b/flags/vc.png
new file mode 100644 (file)
index 0000000..8e50bba
Binary files /dev/null and b/flags/vc.png differ
diff --git a/flags/ve.png b/flags/ve.png
new file mode 100644 (file)
index 0000000..fbb9820
Binary files /dev/null and b/flags/ve.png differ
diff --git a/flags/vg.png b/flags/vg.png
new file mode 100644 (file)
index 0000000..afb867a
Binary files /dev/null and b/flags/vg.png differ
diff --git a/flags/vi.png b/flags/vi.png
new file mode 100644 (file)
index 0000000..afb867a
Binary files /dev/null and b/flags/vi.png differ
diff --git a/flags/vn.png b/flags/vn.png
new file mode 100644 (file)
index 0000000..9b0536f
Binary files /dev/null and b/flags/vn.png differ
diff --git a/flags/vu.png b/flags/vu.png
new file mode 100644 (file)
index 0000000..d609d75
Binary files /dev/null and b/flags/vu.png differ
diff --git a/flags/wf.png b/flags/wf.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/wf.png differ
diff --git a/flags/ws.png b/flags/ws.png
new file mode 100644 (file)
index 0000000..d71b1a6
Binary files /dev/null and b/flags/ws.png differ
diff --git a/flags/ye.png b/flags/ye.png
new file mode 100644 (file)
index 0000000..e8c50f8
Binary files /dev/null and b/flags/ye.png differ
diff --git a/flags/yt.png b/flags/yt.png
new file mode 100644 (file)
index 0000000..2d0efbd
Binary files /dev/null and b/flags/yt.png differ
diff --git a/flags/za.png b/flags/za.png
new file mode 100644 (file)
index 0000000..695b53e
Binary files /dev/null and b/flags/za.png differ
diff --git a/flags/zm.png b/flags/zm.png
new file mode 100644 (file)
index 0000000..2fff587
Binary files /dev/null and b/flags/zm.png differ
diff --git a/flags/zw.png b/flags/zw.png
new file mode 100644 (file)
index 0000000..76af07f
Binary files /dev/null and b/flags/zw.png differ
index 1846c02f860b6ab4cb457f28cf795bd541c85196..e229cce4a17c953dcf401adf7f3a16d58d27e5bc 100644 (file)
@@ -1,4 +1,4 @@
-CONFIG += release
+CONFIG += debug
 TEMPLATE = app
 VERSION = 2.0
 DEFINES += APP_VERSION="$$VERSION"
@@ -13,15 +13,11 @@ DEFINES += QT_USE_FAST_CONCATENATION
 DEFINES += QT_USE_FAST_OPERATOR_PLUS
 DEFINES += QT_STRICT_ITERATORS
 
-# TODO Saner string behaviour
-# DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
 TARGET = minitube
-QT += network xml phonon declarative
+QT += network xml phonon
 include(src/qtsingleapplication/qtsingleapplication.pri)
 HEADERS += \
-    src/youtubesearch.h \
     src/video.h \
-    src/youtubestreamreader.h \
     src/searchlineedit.h \
     src/urllineedit.h \
     src/spacer.h \
@@ -32,7 +28,6 @@ HEADERS += \
     src/videomimedata.h \
     src/global.h \
     src/updatechecker.h \
-    src/playlistwidget.h \
     src/searchparams.h \
     src/minisplitter.h \
     src/loadingwidget.h \
@@ -48,7 +43,6 @@ HEADERS += \
     src/downloadmodel.h \
     src/downloadlistview.h \
     src/downloadsettings.h \
-    src/youtubesuggest.h \
     src/suggester.h \
     src/channelsuggest.h \
     src/temporary.h \
@@ -59,17 +53,25 @@ HEADERS += \
     src/sidebarwidget.h \
     src/homeview.h \
     src/aboutview.h \
-    src/listmodel.h \
     src/mainwindow.h \
     src/mediaview.h \
     src/searchview.h \
     src/view.h \
-    src/categoriesview.h \
     src/userview.h \
-    src/youtubecategories.h
+    src/playlistmodel.h \
+    src/videosource.h \
+    src/ytsearch.h \
+    src/ytstandardfeed.h \
+    src/standardfeedsview.h \
+    src/ytregions.h \
+    src/ytcategories.h \
+    src/ytfeedreader.h \
+    src/ytsuggester.h \
+    src/videosourcewidget.h \
+    src/regionsview.h \
+    src/ytsinglevideosource.h \
+    src/sidebarheader.h
 SOURCES += src/main.cpp \
-    src/youtubesearch.cpp \
-    src/youtubestreamreader.cpp \
     src/searchlineedit.cpp \
     src/urllineedit.cpp \
     src/spacer.cpp \
@@ -78,7 +80,6 @@ SOURCES += src/main.cpp \
     src/videomimedata.cpp \
     src/updatechecker.cpp \
     src/networkaccess.cpp \
-    src/playlistwidget.cpp \
     src/searchparams.cpp \
     src/minisplitter.cpp \
     src/loadingwidget.cpp \
@@ -95,7 +96,6 @@ SOURCES += src/main.cpp \
     src/downloadmodel.cpp \
     src/downloadlistview.cpp \
     src/downloadsettings.cpp \
-    src/youtubesuggest.cpp \
     src/channelsuggest.cpp \
     src/temporary.cpp \
     src/segmentedcontrol.cpp \
@@ -106,13 +106,23 @@ SOURCES += src/main.cpp \
     src/homeview.cpp \
     src/mainwindow.cpp \
     src/mediaview.cpp \
-    src/listmodel.cpp \
     src/aboutview.cpp \
     src/searchview.cpp \
-    src/categoriesview.cpp \
     src/userview.cpp \
     src/playlistitemdelegate.cpp \
-    src/youtubecategories.cpp
+    src/playlistmodel.cpp \
+    src/videosource.cpp \
+    src/ytsearch.cpp \
+    src/ytstandardfeed.cpp \
+    src/standardfeedsview.cpp \
+    src/ytregions.cpp \
+    src/ytcategories.cpp \
+    src/ytfeedreader.cpp \
+    src/ytsuggester.cpp \
+    src/videosourcewidget.cpp \
+    src/regionsview.cpp \
+    src/ytsinglevideosource.cpp \
+    src/sidebarheader.cpp
 RESOURCES += resources.qrc
 DESTDIR = build/target/
 OBJECTS_DIR = build/obj/
@@ -174,6 +184,3 @@ unix:!mac {
     icon512.files += data/512x512/minitube.png
 }
 mac|win32:include(local/local.pri)
-
-OTHER_FILES += \
-    qml/categories.qml
index 5961a8fda384648ac2e370a8cb860d1dda2be835..8206127b5a7e8ffa8cc811924ba32372a500a857 100644 (file)
@@ -6,6 +6,53 @@
         <file>images/search-sortBy.png</file>
         <file>images/search-quality.png</file>
         <file>images/search-duration.png</file>
-        <file>qml/categories.qml</file>
+        <file>flags/dz.png</file>
+        <file>flags/ar.png</file>
+        <file>flags/au.png</file>
+        <file>flags/be.png</file>
+        <file>flags/br.png</file>
+        <file>flags/ca.png</file>
+        <file>flags/cl.png</file>
+        <file>flags/co.png</file>
+        <file>flags/cz.png</file>
+        <file>flags/eg.png</file>
+        <file>flags/fr.png</file>
+        <file>flags/de.png</file>
+        <file>flags/gh.png</file>
+        <file>flags/gr.png</file>
+        <file>flags/hk.png</file>
+        <file>flags/hu.png</file>
+        <file>flags/in.png</file>
+        <file>flags/id.png</file>
+        <file>flags/ie.png</file>
+        <file>flags/il.png</file>
+        <file>flags/it.png</file>
+        <file>flags/jp.png</file>
+        <file>flags/jo.png</file>
+        <file>flags/ke.png</file>
+        <file>flags/my.png</file>
+        <file>flags/mx.png</file>
+        <file>flags/ma.png</file>
+        <file>flags/nl.png</file>
+        <file>flags/nz.png</file>
+        <file>flags/ng.png</file>
+        <file>flags/pe.png</file>
+        <file>flags/ph.png</file>
+        <file>flags/pl.png</file>
+        <file>flags/ru.png</file>
+        <file>flags/sa.png</file>
+        <file>flags/sg.png</file>
+        <file>flags/za.png</file>
+        <file>flags/kr.png</file>
+        <file>flags/es.png</file>
+        <file>flags/se.png</file>
+        <file>flags/tw.png</file>
+        <file>flags/tn.png</file>
+        <file>flags/tr.png</file>
+        <file>flags/ug.png</file>
+        <file>flags/ae.png</file>
+        <file>flags/gb.png</file>
+        <file>flags/ye.png</file>
+        <file>style.css</file>
     </qresource>
 </RCC>
index 702711eff646e912fe2ca70b51e592aed5211981..2031d3f66288413fc0551741d78d5bc4790ce949 100644 (file)
@@ -2,7 +2,7 @@
 #include "downloadmanager.h"
 #include "downloaditem.h"
 #include "video.h"
-#include "listmodel.h"
+#include "playlistmodel.h"
 
 DownloadModel::DownloadModel(DownloadManager *downloadManager, QObject *parent) :
         QAbstractListModel(parent),
index 11b7400bc6849cbb381c73512e12d97c704298d4..42db96b8ec094f5bdca5334d3b0b1f7f8e42cb43 100644 (file)
@@ -4,7 +4,7 @@
 #include "downloadlistview.h"
 #include "downloaditem.h"
 #include "downloadsettings.h"
-#include "listmodel.h"
+#include "playlistmodel.h"
 #include "playlistitemdelegate.h"
 #include "segmentedcontrol.h"
 
diff --git a/src/homeview.cpp b/src/homeview.cpp
new file mode 100644 (file)
index 0000000..f7ad71a
--- /dev/null
@@ -0,0 +1,107 @@
+#include "homeview.h"
+#include "segmentedcontrol.h"
+#include "searchview.h"
+#include "standardfeedsview.h"
+#include "userview.h"
+#include "mainwindow.h"
+#include "mediaview.h"
+#include "ytstandardfeed.h"
+
+HomeView::HomeView(QWidget *parent) : QWidget(parent) {
+    standardFeedsView = 0;
+    userView = 0;
+
+    QBoxLayout *layout = new QVBoxLayout(this);
+    layout->setMargin(0);
+    layout->setSpacing(0);
+
+    setupBar();
+    layout->addWidget(bar);
+
+    stackedWidget = new QStackedWidget();
+    layout->addWidget(stackedWidget);
+
+    searchView = new SearchView();
+    connect(searchView, SIGNAL(search(SearchParams*)),
+            MainWindow::instance(), SLOT(showMedia(SearchParams*)));
+    stackedWidget->addWidget(searchView);
+}
+
+void HomeView::setupBar() {
+    bar = new SegmentedControl(this);
+
+    QAction *action = new QAction(tr("Search"), this);
+    action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
+    action->setStatusTip(tr("Find videos and channels by keyword"));
+    connect(action, SIGNAL(triggered()), SLOT(showSearch()));
+    bar->addAction(action);
+    bar->setCheckedAction(action);
+
+    action = new QAction(tr("Categories"), this);
+    action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
+    action->setStatusTip(tr("Browse videos by category"));
+    connect(action, SIGNAL(triggered()), SLOT(showStandardFeeds()));
+    bar->addAction(action);
+
+    /*
+    action = new QAction(tr("User"), this);
+    action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3));
+    action->setStatusTip(tr("Your favorite videos, subscriptions and playlists"));
+    connect(action, SIGNAL(triggered()), SLOT(showUser()));
+    bar->addAction(action);
+    */
+
+    foreach (QAction* action, bar->actions()) {
+        // action->setEnabled(false);
+        addAction(action);
+        action->setAutoRepeat(false);
+        if (!action->shortcut().isEmpty())
+            action->setStatusTip(
+                        action->statusTip() + " (" +
+                        action->shortcut().toString(QKeySequence::NativeText) + ")");
+    }
+}
+
+void HomeView::showWidget(QWidget *widget) {
+    QWidget* currentWidget = stackedWidget->currentWidget();
+    if (currentWidget == widget) return;
+    QMetaObject::invokeMethod(currentWidget, "disappear");
+    currentWidget->setEnabled(false);
+    stackedWidget->setCurrentWidget(widget);
+    widget->setEnabled(true);
+    QMetaObject::invokeMethod(widget, "appear");
+    bar->setCheckedAction(stackedWidget->currentIndex());
+    // autoChosenView = false;
+    widget->setFocus();
+}
+
+void HomeView::appear() {
+    QMetaObject::invokeMethod(stackedWidget->currentWidget(), "appear");
+}
+
+void HomeView::disappear() {
+    QMetaObject::invokeMethod(stackedWidget->currentWidget(), "disappear");
+}
+
+void HomeView::showSearch() {
+    showWidget(searchView);
+}
+
+void HomeView::showStandardFeeds() {
+    if (!standardFeedsView) {
+        standardFeedsView = new StandardFeedsView();
+        connect(standardFeedsView, SIGNAL(activated(VideoSource*)),
+                MainWindow::instance(),
+                SLOT(showMedia(VideoSource*)));
+        stackedWidget->addWidget(standardFeedsView);
+    }
+    showWidget(standardFeedsView);
+}
+
+void HomeView::showUser() {
+    if (!userView) {
+        userView = new UserView(this);
+        stackedWidget->addWidget(userView);
+    }
+    showWidget(userView);
+}
diff --git a/src/homeview.h b/src/homeview.h
new file mode 100644 (file)
index 0000000..20cb46d
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef HOMEVIEW_H
+#define HOMEVIEW_H
+
+#include <QtGui>
+#include "view.h"
+
+class SegmentedControl;
+class SearchView;
+class StandardFeedsView;
+class UserView;
+
+class HomeView : public QWidget, public View  {
+
+    Q_OBJECT
+
+public:
+    HomeView(QWidget *parent = 0);
+    void appear();
+    void disappear();
+    void showWidget(QWidget *widget);
+    SearchView* getSearchView() { return searchView; }
+    StandardFeedsView* getStandardFeedsView() { return standardFeedsView; }
+
+public slots:
+    void showSearch();
+    void showStandardFeeds();
+    void showUser();
+
+private:
+    void setupBar();
+    SegmentedControl *bar;
+    QStackedWidget *stackedWidget;
+
+    SearchView *searchView;
+    StandardFeedsView *standardFeedsView;
+    UserView* userView;
+
+};
+
+#endif // HOMEVIEW_H
index 02b4abe3ddc7fcaf13000880bf07eb73c0038541..3513dbaa774f5fb1f9b9e8e0796320eb05a185c2 100644 (file)
@@ -36,13 +36,16 @@ int main(int argc, char **argv) {
 
 #ifndef Q_WS_X11
     Extra::appSetup(&app);
+#else
+    QFile cssFile(":/style.css");
+    cssFile.open(QFile::ReadOnly);
+    QString styleSheet = QLatin1String(cssFile.readAll());
+    app.setStyleSheet(styleSheet);
 #endif
 
-    const QString locale = QLocale::system().name();
-
     // qt translations
     QTranslator qtTranslator;
-    qtTranslator.load("qt_" + locale,
+    qtTranslator.load("qt_" + QLocale::system().name(),
                       QLibraryInfo::location(QLibraryInfo::TranslationsPath));
     app.installTranslator(&qtTranslator);
 
@@ -52,13 +55,13 @@ int main(int argc, char **argv) {
 #else
     QString dataDir = "";
 #endif
-    QString localeDir = qApp->applicationDirPath() + QDir::separator() + "locale";
+    QString localeDir = qApp->applicationDirPath() + "/locale";
     if (!QDir(localeDir).exists()) {
-        localeDir = dataDir + QDir::separator() + "locale";
+        localeDir = dataDir + "/locale";
     }
     // qDebug() << "Using locale dir" << localeDir << locale;
     QTranslator translator;
-    translator.load(locale, localeDir);
+    translator.load(QLocale::system(), localeDir);
     app.installTranslator(&translator);
     QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8"));
 
index c427d91f9aa7fc8c9571d4d3d3867d5d0c2917b6..d418eceadb39ff3b4da277781603167aa0674ee2 100644 (file)
@@ -11,6 +11,9 @@
 #include "videodefinition.h"
 #include "fontutils.h"
 #include "globalshortcuts.h"
+#include "searchparams.h"
+#include "videosource.h"
+#include "ytsearch.h"
 #ifdef Q_WS_X11
 #include "gnomeglobalshortcutbackend.h"
 #endif
@@ -21,7 +24,7 @@
 #include "macutils.h"
 #endif
 #include "downloadmanager.h"
-#include "youtubesuggest.h"
+#include "ytsuggester.h"
 #include "updatechecker.h"
 #include "temporary.h"
 #ifdef APP_MAC
@@ -39,6 +42,9 @@
 #include "activationview.h"
 #include "activationdialog.h"
 #endif
+#include "ytregions.h"
+#include "regionsview.h"
+#include "standardfeedsview.h"
 
 static MainWindow *singleton = 0;
 
@@ -48,12 +54,13 @@ MainWindow* MainWindow::instance() {
 }
 
 MainWindow::MainWindow() :
-        updateChecker(0),
-        aboutView(0),
-        downloadView(0),
-        mediaObject(0),
-        audioOutput(0),
-        m_fullscreen(false) {
+    updateChecker(0),
+    aboutView(0),
+    downloadView(0),
+    regionsView(0),
+    mediaObject(0),
+    audioOutput(0),
+    m_fullscreen(false) {
 
     singleton = this;
 
@@ -66,7 +73,7 @@ MainWindow::MainWindow() :
     views->addWidget(homeView);
 
     // TODO make this lazy
-    mediaView = new MediaView(this);
+    mediaView = MediaView::instance();
     mediaView->setEnabled(false);
     views->addWidget(mediaView);
 
@@ -77,7 +84,6 @@ MainWindow::MainWindow() :
     createStatusBar();
 
     initPhonon();
-    mediaView->setSlider(seekSlider);
     mediaView->setMediaObject(mediaObject);
 
     // remove that useless menu/toolbar context menu
@@ -479,8 +485,18 @@ void MainWindow::createActions() {
     action = new QAction(tr("&Refine Search..."), this);
     action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
     action->setCheckable(true);
+    action->setEnabled(false);
     actions->insert("refine-search", action);
 
+    action = new QAction(YTRegions::worldwideRegion().name, this);
+    actions->insert("worldwide-region", action);
+
+    action = new QAction(YTRegions::localRegion().name, this);
+    actions->insert("local-region", action);
+
+    action = new QAction(tr("More..."), this);
+    actions->insert("more-region", action);
+
 #ifdef APP_ACTIVATION
     Extra::createActivationAction(tr("Buy %1...").arg(Constants::NAME));
 #endif
@@ -635,7 +651,7 @@ void MainWindow::createToolBars() {
     seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
     mainToolBar->addWidget(seekSlider);
 
-/*
+    /*
     mainToolBar->addWidget(new Spacer());
     slider = new QSlider(this);
     slider->setOrientation(Qt::Horizontal);
@@ -660,7 +676,7 @@ void MainWindow::createToolBars() {
     QSlider* volumeQSlider = volumeSlider->findChild<QSlider*>();
     if (volumeQSlider)
         volumeQSlider->setStatusTip(tr("Press %1 to raise the volume, %2 to lower it").arg(
-                volumeUpAct->shortcut().toString(QKeySequence::NativeText), volumeDownAct->shortcut().toString(QKeySequence::NativeText)));
+                                        volumeUpAct->shortcut().toString(QKeySequence::NativeText), volumeDownAct->shortcut().toString(QKeySequence::NativeText)));
     // this makes the volume slider smaller
     volumeSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
     mainToolBar->addWidget(volumeSlider);
@@ -674,7 +690,7 @@ void MainWindow::createToolBars() {
     toolbarSearch = new SearchLineEdit(this);
 #endif
     toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize()*15);
-    toolbarSearch->setSuggester(new YouTubeSuggest(this));
+    toolbarSearch->setSuggester(new YTSuggester(this));
     connect(toolbarSearch, SIGNAL(search(const QString&)), this, SLOT(startToolbarSearch(const QString&)));
     connect(toolbarSearch, SIGNAL(suggestionAccepted(const QString&)), SLOT(startToolbarSearch(const QString&)));
     toolbarSearch->setStatusTip(searchFocusAct->statusTip());
@@ -691,13 +707,42 @@ void MainWindow::createToolBars() {
 }
 
 void MainWindow::createStatusBar() {
-    QToolBar* toolBar = new QToolBar(this);
-    statusToolBar = toolBar;
-    toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
-    toolBar->setIconSize(QSize(16, 16));
-    toolBar->addAction(The::globalActions()->value("downloads"));
-    toolBar->addAction(The::globalActions()->value("definition"));
-    statusBar()->addPermanentWidget(toolBar);
+    statusToolBar = new QToolBar(this);
+    statusToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+    statusToolBar->setIconSize(QSize(16, 16));
+    statusToolBar->addAction(The::globalActions()->value("downloads"));
+
+    regionButton = new QToolButton(this);
+    regionButton->setStatusTip(tr("Choose your content location"));
+    regionButton->setIcon(QtIconLoader::icon("go-down"));
+    regionButton->setIconSize(QSize(16, 16));
+    regionButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+    regionAction = statusToolBar->addWidget(regionButton);
+    regionAction->setVisible(false);
+
+    QAction *localAction = The::globalActions()->value("local-region");
+    if (!localAction->text().isEmpty()) {
+        regionButton->setPopupMode(QToolButton::InstantPopup);
+        QMenu *regionMenu = new QMenu(this);
+        regionMenu->addAction(The::globalActions()->value("worldwide-region"));
+        regionMenu->addAction(localAction);
+        regionMenu->addSeparator();
+        QAction *moreRegionsAction = The::globalActions()->value("more-region");
+        regionMenu->addAction(moreRegionsAction);
+        connect(moreRegionsAction, SIGNAL(triggered()), SLOT(showRegionsView()));
+        regionButton->setMenu(regionMenu);
+    } else {
+        connect(regionButton, SIGNAL(clicked()), SLOT(showRegionsView()));
+    }
+
+    /* Stupid code that generates the QRC items
+    foreach(YTRegion r, YTRegions::list())
+        qDebug() << QString("<file>flags/%1.png</file>").arg(r.id.toLower());
+    */
+
+    statusToolBar->addAction(The::globalActions()->value("definition"));
+
+    statusBar()->addPermanentWidget(statusToolBar);
     statusBar()->show();
 }
 
@@ -719,7 +764,7 @@ void MainWindow::readSettings() {
     if (settings.contains("geometry")) {
         restoreGeometry(settings.value("geometry").toByteArray());
 #ifdef APP_MAC
-    MacSupport::fixGeometry(this);
+        MacSupport::fixGeometry(this);
 #endif
     } else {
         setGeometry(100, 100, 1000, 500);
@@ -770,8 +815,9 @@ void MainWindow::showWidget(QWidget* widget, bool transition) {
         newView->appear();
         QHash<QString,QVariant> metadata = newView->metadata();
         QString title = metadata.value("title").toString();
-        if (!title.isEmpty()) title += " - ";
-        setWindowTitle(title + Constants::NAME);
+        if (title.isEmpty()) title = Constants::NAME;
+        else title += QLatin1String(" - ") + Constants::NAME;
+        setWindowTitle(title);
         QString desc = metadata.value("description").toString();
         if (!desc.isEmpty()) showMessage(desc);
     }
@@ -811,7 +857,8 @@ void MainWindow::showWidget(QWidget* widget, bool transition) {
     widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
 
 #ifndef Q_WS_X11
-    if (transition) Extra::fadeInWidget(oldWidget, widget);
+    if (transition && oldWidget != mediaView)
+        Extra::fadeInWidget(oldWidget, widget);
 #endif
 
     history->push(widget);
@@ -903,6 +950,11 @@ void MainWindow::showMedia(SearchParams *searchParams) {
     showWidget(mediaView);
 }
 
+void MainWindow::showMedia(VideoSource *videoSource) {
+    mediaView->setVideoSource(videoSource);
+    showWidget(mediaView);
+}
+
 void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState */) {
 
     // qDebug() << "Phonon state: " << newState;
@@ -919,7 +971,7 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         }
         break;
 
-         case Phonon::PlayingState:
+    case Phonon::PlayingState:
         pauseAct->setEnabled(true);
         pauseAct->setIcon(QtIconLoader::icon("media-playback-pause"));
         pauseAct->setText(tr("&Pause"));
@@ -927,12 +979,12 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         // stopAct->setEnabled(true);
         break;
 
-         case Phonon::StoppedState:
+    case Phonon::StoppedState:
         pauseAct->setEnabled(false);
         // stopAct->setEnabled(false);
         break;
 
-         case Phonon::PausedState:
+    case Phonon::PausedState:
         pauseAct->setEnabled(true);
         pauseAct->setIcon(QtIconLoader::icon("media-playback-start"));
         pauseAct->setText(tr("&Play"));
@@ -940,15 +992,15 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         // stopAct->setEnabled(true);
         break;
 
-         case Phonon::BufferingState:
-         case Phonon::LoadingState:
+    case Phonon::BufferingState:
+    case Phonon::LoadingState:
         pauseAct->setEnabled(false);
         currentTime->clear();
         totalTime->clear();
         // stopAct->setEnabled(true);
         break;
 
-         default:
+    default:
         ;
     }
 }
@@ -1305,11 +1357,11 @@ void MainWindow::toggleDownloads(bool show) {
     if (show) {
         stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_MediaStop));
         The::globalActions()->value("downloads")->setShortcuts(
-                QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J)
-                << QKeySequence(Qt::Key_Escape));
+                    QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J)
+                    << QKeySequence(Qt::Key_Escape));
     } else {
         The::globalActions()->value("downloads")->setShortcuts(
-                QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J));
+                    QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J));
         stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
     }
 
@@ -1342,7 +1394,7 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event) {
         if (urls.isEmpty())
             return;
         QUrl url = urls.first();
-        QString videoId = YouTubeSearch::videoIdFromUrl(url.toString());
+        QString videoId = YTSearch::videoIdFromUrl(url.toString());
         if (!videoId.isNull())
             event->acceptProposedAction();
     }
@@ -1353,7 +1405,7 @@ void MainWindow::dropEvent(QDropEvent *event) {
     if (urls.isEmpty())
         return;
     QUrl url = urls.first();
-    QString videoId = YouTubeSearch::videoIdFromUrl(url.toString());
+    QString videoId = YTSearch::videoIdFromUrl(url.toString());
     if (!videoId.isNull()) {
         setWindowTitle(url.toString());
         SearchParams *searchParams = new SearchParams();
@@ -1507,3 +1559,13 @@ void MainWindow::hideBuyAction() {
     action->setEnabled(false);
 }
 #endif
+
+void MainWindow::showRegionsView() {
+    if (!regionsView) {
+        regionsView = new RegionsView(this);
+        connect(regionsView, SIGNAL(regionChanged()),
+                homeView->getStandardFeedsView(), SLOT(load()));
+        views->addWidget(regionsView);
+    }
+    showWidget(regionsView);
+}
index cdb66f5ec4ce58b2bcf8c533445dee210dd145b9..160274821ae8125588a86b6fbb198385421edbd5 100644 (file)
@@ -14,6 +14,7 @@ class DownloadView;
 class SearchLineEdit;
 class UpdateChecker;
 class SearchParams;
+class VideoSource;
 
 class MainWindow : public QMainWindow {
 
@@ -27,10 +28,15 @@ public:
     void readSettings();
     void writeSettings();
     static void printHelp();
+    MediaView* getMediaView() { return mediaView; }
+    QToolButton* getRegionButton() { return regionButton; }
+    QAction* getRegionAction() { return regionAction; }
 
 public slots:
     void showHome(bool transition = true);
     void showMedia(SearchParams *params);
+    void showMedia(VideoSource *videoSource);
+    void showRegionsView();
     void restore();
     void messageReceived(const QString &message);
     void quit();
@@ -115,6 +121,7 @@ private:
     MediaView *mediaView;
     QWidget *aboutView;
     QWidget *downloadView;
+    QWidget *regionsView;
 
     // actions
     QAction *addGadgetAct;
@@ -153,10 +160,12 @@ private:
     QMenu *playlistMenu;
     QMenu *helpMenu;
 
-    // toolbar
+    // toolbar & statusbar
     QToolBar *mainToolBar;
     SearchLineEdit *toolbarSearch;
     QToolBar *statusToolBar;
+    QToolButton *regionButton;
+    QAction *regionAction;
 
     // phonon
     Phonon::SeekSlider *seekSlider;
@@ -166,11 +175,10 @@ private:
     QLabel *currentTime;
     QLabel *totalTime;
 
+    // fullscreen
     bool m_fullscreen;
     bool m_maximized;
-
     QTimer *mouseTimer;
-
 };
 
 #endif
index 32540b3ebd43e6e6edd07886e96eb831240cd814..634696199dbd45f4396e21ce82ae599656774b06 100644 (file)
@@ -1,6 +1,8 @@
 #include "mediaview.h"
+#include "playlistmodel.h"
 #include "playlistview.h"
-#include "playlistitemdelegate.h"
+#include "loadingwidget.h"
+#include "videoareawidget.h"
 #include "networkaccess.h"
 #include "videowidget.h"
 #include "minisplitter.h"
 #include "downloaditem.h"
 #include "mainwindow.h"
 #include "temporary.h"
-#include "sidebarwidget.h"
-#include "playlistwidget.h"
 #include "refinesearchwidget.h"
 #include "sidebarwidget.h"
+#include "sidebarheader.h"
 #ifdef APP_MAC
 #include "macfullscreen.h"
+#include "macutils.h"
 #endif
 #ifdef APP_ACTIVATION
 #include "activation.h"
 #endif
+#include "videosource.h"
+#include "ytsearch.h"
+#include "searchparams.h"
+#include "ytsinglevideosource.h"
 
 namespace The {
 NetworkAccess* http();
-}
-
-namespace The {
 QMap<QString, QAction*>* globalActions();
 QMap<QString, QMenu*>* globalMenus();
 QNetworkAccessManager* networkAccessManager();
 }
 
-MediaView::MediaView(QWidget *parent) : QWidget(parent) {
+MediaView* MediaView::instance() {
+    static MediaView *i = new MediaView();
+    return i;
+}
 
+MediaView::MediaView(QWidget *parent) : QWidget(parent) {
     reallyStopped = false;
     downloadItem = 0;
 
-    QBoxLayout *layout = new QVBoxLayout();
+    QBoxLayout *layout = new QVBoxLayout(this);
     layout->setMargin(0);
 
-    splitter = new MiniSplitter(this);
+    splitter = new MiniSplitter();
     splitter->setChildrenCollapsible(false);
 
-    listView = new PlaylistView(this);
-    listView->setItemDelegate(new PlaylistItemDelegate(this));
-    listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
-
-    // dragndrop
-    listView->setDragEnabled(true);
-    listView->setAcceptDrops(true);
-    listView->setDropIndicatorShown(true);
-    listView->setDragDropMode(QAbstractItemView::DragDrop);
-
-    // cosmetics
-    listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
-    listView->setFrameShape( QFrame::NoFrame );
-    listView->setAttribute(Qt::WA_MacShowFocusRect, false);
-    listView->setMinimumSize(320,240);
-    listView->setUniformItemSizes(true);
-
+    playlistView = new PlaylistView(this);
     // respond to the user doubleclicking a playlist item
-    connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
+    connect(playlistView, SIGNAL(activated(const QModelIndex &)),
+            SLOT(itemActivated(const QModelIndex &)));
 
-    listModel = new ListModel(this);
-    connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
+    playlistModel = new PlaylistModel();
+    connect(playlistModel, SIGNAL(activeRowChanged(int)),
+            SLOT(activeRowChanged(int)));
     // needed to restore the selection after dragndrop
-    connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
-    listView->setModel(listModel);
+    connect(playlistModel, SIGNAL(needSelectionFor(QList<Video*>)),
+            SLOT(selectVideos(QList<Video*>)));
+    playlistView->setModel(playlistModel);
 
-    connect(listView->selectionModel(),
-            SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
-            this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
+    connect(playlistView->selectionModel(),
+            SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+            SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
 
-    connect(listView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
+    connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
 
     sidebar = new SidebarWidget(this);
-    sidebar->setPlaylist(listView);
+    sidebar->setPlaylist(playlistView);
     connect(sidebar->getRefineSearchWidget(), SIGNAL(searchRefined()),
             SLOT(searchAgain()));
-    connect(listModel, SIGNAL(haveSuggestions(const QStringList &)),
+    connect(playlistModel, SIGNAL(haveSuggestions(const QStringList &)),
             sidebar, SLOT(showSuggestions(const QStringList &)));
     connect(sidebar, SIGNAL(suggestionAccepted(QString)),
             MainWindow::instance(), SLOT(startToolbarSearch(QString)));
@@ -87,7 +81,7 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) {
     videoAreaWidget->setMinimumSize(320,240);
     videoWidget = new Phonon::VideoWidget(this);
     videoAreaWidget->setVideoWidget(videoWidget);
-    videoAreaWidget->setListModel(listModel);
+    videoAreaWidget->setListModel(playlistModel);
 
     loadingWidget = new LoadingWidget(this);
     videoAreaWidget->setLoadingWidget(loadingWidget);
@@ -95,7 +89,6 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) {
     splitter->addWidget(videoAreaWidget);
 
     layout->addWidget(splitter);
-    setLayout(layout);
 
     splitter->setStretchFactor(0, 1);
     splitter->setStretchFactor(1, 6);
@@ -109,11 +102,6 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) {
     errorTimer->setInterval(3000);
     connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
 
-    workaroundTimer = new QTimer(this);
-    workaroundTimer->setSingleShot(true);
-    workaroundTimer->setInterval(3000);
-    connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
-
 #ifdef APP_ACTIVATION
     demoTimer = new QTimer(this);
     demoTimer->setSingleShot(true);
@@ -123,13 +111,8 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) {
 }
 
 void MediaView::initialize() {
-    connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
-
-    /*
-    videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu);
-    connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)),
-            this, SLOT(showVideoContextMenu(QPoint)));
-            */
+    connect(videoAreaWidget, SIGNAL(doubleClicked()),
+            The::globalActions()->value("fullscreen"), SLOT(trigger()));
 
     QAction* refineSearchAction = The::globalActions()->value("refine-search");
     connect(refineSearchAction, SIGNAL(toggled(bool)),
@@ -138,121 +121,131 @@ void MediaView::initialize() {
 
 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
     this->mediaObject = mediaObject;
-    Phonon::createPath(this->mediaObject, videoWidget);
-    connect(mediaObject, SIGNAL(finished()), this, SLOT(playbackFinished()));
+    Phonon::createPath(mediaObject, videoWidget);
+    connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
     connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
-            this, SLOT(stateChanged(Phonon::State, Phonon::State)));
+            SLOT(stateChanged(Phonon::State, Phonon::State)));
     connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
-            this, SLOT(currentSourceChanged(Phonon::MediaSource)));
-    // connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
+            SLOT(currentSourceChanged(Phonon::MediaSource)));
     connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
 }
 
+SearchParams* MediaView::getSearchParams() {
+    VideoSource *videoSource = playlistModel->getVideoSource();
+    if (videoSource && videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
+        YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
+        return search->getSearchParams();
+    }
+    return 0;
+}
+
 void MediaView::search(SearchParams *searchParams) {
+    if (!searchParams->keywords().isEmpty()) {
+        if (searchParams->keywords().startsWith("http://") ||
+                searchParams->keywords().startsWith("https://")) {
+            QString videoId = YTSearch::videoIdFromUrl(searchParams->keywords());
+            if (!videoId.isEmpty()) {
+                qDebug() << "single video";
+                YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource(this);
+                singleVideoSource->setVideoId(videoId);
+                setVideoSource(singleVideoSource);
+                return;
+            }
+        }
+    }
+    setVideoSource(new YTSearch(searchParams, this));
+}
+
+void MediaView::setVideoSource(VideoSource *videoSource, bool addToHistory) {
     reallyStopped = false;
 
 #ifdef APP_ACTIVATION
     demoTimer->stop();
 #endif
-    workaroundTimer->stop();
     errorTimer->stop();
 
-    this->searchParams = searchParams;
+    if (addToHistory) {
+        int currentIndex = getHistoryIndex();
+        if (currentIndex >= 0 && currentIndex < history.size() - 1) {
+            for (int i = currentIndex + 1; i < history.size(); i++) {
+                VideoSource *vs = history.takeAt(i);
+                if (!vs->parent()) delete vs;
+            }
+        }
+        history.append(videoSource);
+    }
 
-    // start serching for videos
-    listModel->search(searchParams);
+    playlistModel->setVideoSource(videoSource);
 
     sidebar->showPlaylist();
-    listView->setFocus();
-
-    QString keyword = searchParams->keywords();
-    QString display = keyword;
-    if (keyword.startsWith("http://") || keyword.startsWith("https://")) {
-        int separator = keyword.indexOf("|");
-        if (separator > 0 && separator + 1 < keyword.length()) {
-            display = keyword.mid(separator+1);
+    sidebar->getRefineSearchWidget()->setSearchParams(getSearchParams());
+    sidebar->hideSuggestions();
+    sidebar->getHeader()->updateInfo();
+
+    SearchParams *searchParams = getSearchParams();
+    bool isChannel = searchParams && !searchParams->author().isEmpty();
+    playlistView->setClickableAuthors(!isChannel);
+}
+
+void MediaView::searchAgain() {
+    VideoSource *currentVideoSource = playlistModel->getVideoSource();
+    setVideoSource(currentVideoSource, false);
+}
+
+bool MediaView::canGoBack() {
+    return getHistoryIndex() > 0;
+}
+
+void MediaView::goBack() {
+    if (history.size() > 1) {
+        int currentIndex = getHistoryIndex();
+        if (currentIndex > 0) {
+            VideoSource *previousVideoSource = history.at(currentIndex - 1);
+            setVideoSource(previousVideoSource, false);
         }
     }
+}
 
-    sidebar->getRefineSearchWidget()->setSearchParams(searchParams);
-    sidebar->hideSuggestions();
+bool MediaView::canGoForward() {
+    int currentIndex = getHistoryIndex();
+    return currentIndex >= 0 && currentIndex < history.size() - 1;
+}
 
+void MediaView::goForward() {
+    if (canGoForward()) {
+        int currentIndex = getHistoryIndex();
+        VideoSource *nextVideoSource = history.at(currentIndex + 1);
+        setVideoSource(nextVideoSource, false);
+    }
 }
 
-void MediaView::searchAgain() {
-    search(searchParams);
+int MediaView::getHistoryIndex() {
+    return history.lastIndexOf(playlistModel->getVideoSource());
 }
 
 void MediaView::appear() {
-    listView->setFocus();
+    playlistView->setFocus();
 }
 
 void MediaView::disappear() {
-    timerPlayFlag = true;
+
 }
 
 void MediaView::handleError(QString /* message */) {
-
     QTimer::singleShot(500, this, SLOT(startPlaying()));
-
-    /*
-    videoAreaWidget->showError(message);
-    skippedVideo = listModel->activeVideo();
-    // recover from errors by skipping to the next video
-    errorTimer->start(2000);
-    */
 }
 
 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) {
-    // qDebug() << "Phonon state: " << newState;
-    // slider->setEnabled(newState == Phonon::PlayingState);
-
-    switch (newState) {
-
-    case Phonon::ErrorState:
+    if (newState == Phonon::PlayingState)
+        videoAreaWidget->showVideo();
+    else if (newState == Phonon::ErrorState) {
         qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
         if (mediaObject->errorType() == Phonon::FatalError)
             handleError(mediaObject->errorString());
-        break;
-
-    case Phonon::PlayingState:
-        // qDebug("playing");
-        videoAreaWidget->showVideo();
-        break;
-
-    case Phonon::StoppedState:
-        // qDebug("stopped");
-        // play() has already been called when setting the source
-        // but Phonon on Linux needs a little more help to start playback
-        // if (!reallyStopped) mediaObject->play();
-
-#ifdef APP_MAC
-        // Workaround for Mac playback start problem
-        if (!timerPlayFlag) {
-            // workaroundTimer->start();
-        }
-#endif
-
-        break;
-
-    case Phonon::PausedState:
-        qDebug("paused");
-        break;
-
-    case Phonon::BufferingState:
-        qDebug("buffering");
-        break;
-
-    case Phonon::LoadingState:
-        qDebug("loading");
-        break;
-
     }
 }
 
 void MediaView::pause() {
-    // qDebug() << "pause() called" << mediaObject->state();
-
     switch( mediaObject->state() ) {
     case Phonon::PlayingState:
         mediaObject->pause();
@@ -261,7 +254,6 @@ void MediaView::pause() {
         mediaObject->play();
         break;
     }
-
 }
 
 QRegExp MediaView::wordRE(QString s) {
@@ -269,60 +261,68 @@ QRegExp MediaView::wordRE(QString s) {
 }
 
 void MediaView::stop() {
-    listModel->abortSearch();
+    playlistModel->abortSearch();
     reallyStopped = true;
     mediaObject->stop();
     videoAreaWidget->clear();
-    workaroundTimer->stop();
     errorTimer->stop();
-    listView->selectionModel()->clearSelection();
+    playlistView->selectionModel()->clearSelection();
     if (downloadItem) {
         downloadItem->stop();
         delete downloadItem;
         downloadItem = 0;
     }
     The::globalActions()->value("refine-search")->setChecked(false);
+
+    while (!history.isEmpty()) {
+        VideoSource *videoSource = history.takeFirst();
+        if (!videoSource->parent()) delete videoSource;
+    }
 }
 
 void MediaView::activeRowChanged(int row) {
     if (reallyStopped) return;
 
-    Video *video = listModel->videoAt(row);
+    Video *video = playlistModel->videoAt(row);
     if (!video) return;
 
-    // now that we have a new video to play
-    // stop all the timers
-    workaroundTimer->stop();
+    // Related videos without video interruption
+    if (row == 0) {
+        VideoSource *videoSource = playlistModel->getVideoSource();
+        if (videoSource && !history.isEmpty() &&
+                mediaObject->state() == Phonon::PlayingState &&
+                videoSource->metaObject()->className() == QLatin1String("YTSingleVideoSource")) {
+            if (playlistModel->videoAt(row)->title() == downloadItem->getVideo()->title())
+                return;
+        }
+    }
+
     errorTimer->stop();
 
+    videoAreaWidget->showLoading(video);
+
     mediaObject->stop();
     if (downloadItem) {
         downloadItem->stop();
         delete downloadItem;
         downloadItem = 0;
     }
-    // slider->setMinimum(0);
-
-    // immediately show the loading widget
-    videoAreaWidget->showLoading(video);
 
-    connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
-    // TODO handle signal in a proper slot and impl item error status
-    connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)), Qt::UniqueConnection);
+    connect(video, SIGNAL(gotStreamUrl(QUrl)),
+            SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
+    connect(video, SIGNAL(errorStreamUrl(QString)),
+            SLOT(handleError(QString)), Qt::UniqueConnection);
 
     video->loadStreamUrl();
 
-    // reset the timer flag
-    timerPlayFlag = false;
-
     // video title in the statusbar
     MainWindow::instance()->showMessage(video->title());
 
     // ensure active item is visible
     // int row = listModel->activeRow();
     if (row != -1) {
-        QModelIndex index = listModel->index(row, 0, QModelIndex());
-        listView->scrollTo(index, QAbstractItemView::EnsureVisible);
+        QModelIndex index = playlistModel->index(row, 0, QModelIndex());
+        playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
     }
 
     // enable/disable actions
@@ -340,7 +340,7 @@ void MediaView::gotStreamUrl(QUrl streamUrl) {
 
     Video *video = static_cast<Video *>(sender());
     if (!video) {
-        qDebug() << "Cannot get sender";
+        qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
         return;
     }
     video->disconnect(this);
@@ -361,6 +361,10 @@ void MediaView::gotStreamUrl(QUrl streamUrl) {
     connect(downloadItem, SIGNAL(error(QString)), SLOT(handleError(QString)), Qt::UniqueConnection);
     downloadItem->start();
 
+#ifdef Q_WS_MAC
+    if (mac::canNotify())
+        mac::notify(video->title(), video->author(), video->formattedDuration());
+#endif
 }
 
 /*
@@ -403,7 +407,7 @@ void MediaView::downloadStatusChanged() {
         // qDebug() << "Finished" << mediaObject->state();
         // if (mediaObject->state() == Phonon::StoppedState) startPlaying();
 #ifdef Q_WS_X11
-        seekSlider->setEnabled(mediaObject->isSeekable());
+        MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
 #endif
         break;
     case Failed:
@@ -427,17 +431,17 @@ void MediaView::startPlaying() {
     mediaObject->setCurrentSource(source);
     mediaObject->play();
 #ifdef Q_WS_X11
-    seekSlider->setEnabled(false);
+    MainWindow::instance()->getSeekSlider()->setEnabled(false);
 #endif
 
     // ensure we always have 10 videos ahead
-    listModel->searchNeeded();
+    playlistModel->searchNeeded();
 
     // ensure active item is visible
-    int row = listModel->activeRow();
+    int row = playlistModel->activeRow();
     if (row != -1) {
-        QModelIndex index = listModel->index(row, 0, QModelIndex());
-        listView->scrollTo(index, QAbstractItemView::EnsureVisible);
+        QModelIndex index = playlistModel->index(row, 0, QModelIndex());
+        playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
     }
 
 #ifdef APP_ACTIVATION
@@ -448,20 +452,20 @@ void MediaView::startPlaying() {
 }
 
 void MediaView::itemActivated(const QModelIndex &index) {
-    if (listModel->rowExists(index.row())) {
+    if (playlistModel->rowExists(index.row())) {
 
         // if it's the current video, just rewind and play
-        Video *activeVideo = listModel->activeVideo();
-        Video *video = listModel->videoAt(index.row());
+        Video *activeVideo = playlistModel->activeVideo();
+        Video *video = playlistModel->videoAt(index.row());
         if (activeVideo && video && activeVideo == video) {
             mediaObject->seek(0);
             mediaObject->play();
-        } else listModel->setActiveRow(index.row());
+        } else playlistModel->setActiveRow(index.row());
 
-    // the user doubleclicked on the "Search More" item
+        // the user doubleclicked on the "Search More" item
     } else {
-        listModel->searchMore();
-        listView->selectionModel()->clearSelection();
+        playlistModel->searchMore();
+        playlistView->selectionModel()->clearSelection();
     }
 }
 
@@ -474,27 +478,27 @@ void MediaView::skipVideo() {
     // in order to be sure that we're skipping the video we wanted
     // and not another one
     if (skippedVideo) {
-        if (listModel->activeVideo() != skippedVideo) {
+        if (playlistModel->activeVideo() != skippedVideo) {
             qDebug() << "Skip of video canceled";
             return;
         }
-        int nextRow = listModel->rowForVideo(skippedVideo);
+        int nextRow = playlistModel->rowForVideo(skippedVideo);
         nextRow++;
         if (nextRow == -1) return;
-        listModel->setActiveRow(nextRow);
+        playlistModel->setActiveRow(nextRow);
     }
 }
 
 void MediaView::skip() {
-    int nextRow = listModel->nextRow();
+    int nextRow = playlistModel->nextRow();
     if (nextRow == -1) return;
-    listModel->setActiveRow(nextRow);
+    playlistModel->setActiveRow(nextRow);
 }
 
 void MediaView::skipBackward() {
-    int prevRow = listModel->previousRow();
+    int prevRow = playlistModel->previousRow();
     if (prevRow == -1) return;
-    listModel->setActiveRow(prevRow);
+    playlistModel->setActiveRow(prevRow);
 }
 
 void MediaView::aboutToFinish() {
@@ -530,14 +534,14 @@ void MediaView::playbackResume() {
 }
 
 void MediaView::openWebPage() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     mediaObject->pause();
     QDesktopServices::openUrl(video->webpage());
 }
 
 void MediaView::copyWebPage() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QString address = video->webpage().toString();
     QApplication::clipboard()->setText(address);
@@ -546,7 +550,7 @@ void MediaView::copyWebPage() {
 }
 
 void MediaView::copyVideoLink() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
     QString message = tr("You can now paste the video stream URL into another application")
@@ -555,90 +559,60 @@ void MediaView::copyVideoLink() {
 }
 
 void MediaView::removeSelected() {
-    if (!listView->selectionModel()->hasSelection()) return;
-    QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
-    listModel->removeIndexes(indexes);
+    if (!playlistView->selectionModel()->hasSelection()) return;
+    QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
+    playlistModel->removeIndexes(indexes);
 }
 
 void MediaView::selectVideos(QList<Video*> videos) {
     foreach (Video *video, videos) {
-        QModelIndex index = listModel->indexForVideo(video);
-        listView->selectionModel()->select(index, QItemSelectionModel::Select);
-        listView->scrollTo(index, QAbstractItemView::EnsureVisible);
+        QModelIndex index = playlistModel->indexForVideo(video);
+        playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
+        playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
     }
 }
 
 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
-    const bool gotSelection = listView->selectionModel()->hasSelection();
+    const bool gotSelection = playlistView->selectionModel()->hasSelection();
     The::globalActions()->value("remove")->setEnabled(gotSelection);
     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
 }
 
 void MediaView::moveUpSelected() {
-    if (!listView->selectionModel()->hasSelection()) return;
+    if (!playlistView->selectionModel()->hasSelection()) return;
 
-    QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
+    QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
     qStableSort(indexes.begin(), indexes.end());
-    listModel->move(indexes, true);
+    playlistModel->move(indexes, true);
 
     // set current index after row moves to something more intuitive
     int row = indexes.first().row();
-    listView->selectionModel()->setCurrentIndex(listModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
+    playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
 }
 
 void MediaView::moveDownSelected() {
-    if (!listView->selectionModel()->hasSelection()) return;
+    if (!playlistView->selectionModel()->hasSelection()) return;
 
-    QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
+    QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
     qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
-    listModel->move(indexes, false);
+    playlistModel->move(indexes, false);
 
     // set current index after row moves to something more intuitive (respect 1 static item on bottom)
-    int row = indexes.first().row()+1, max = listModel->rowCount() - 2;
-    listView->selectionModel()->setCurrentIndex(listModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
-}
-
-void MediaView::showVideoContextMenu(QPoint point) {
-    The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
-}
-
-void MediaView::searchMostRelevant() {
-    searchParams->setSortBy(SearchParams::SortByRelevance);
-    search(searchParams);
-}
-
-void MediaView::searchMostRecent() {
-    searchParams->setSortBy(SearchParams::SortByNewest);
-    search(searchParams);
-}
-
-void MediaView::searchMostViewed() {
-    searchParams->setSortBy(SearchParams::SortByViewCount);
-    search(searchParams);
+    int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
+    playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
 }
 
 void MediaView::setPlaylistVisible(bool visible) {
     if (splitter->widget(0)->isVisible() == visible) return;
     splitter->widget(0)->setVisible(visible);
-    listView->setFocus();
+    playlistView->setFocus();
 }
 
 bool MediaView::isPlaylistVisible() {
     return splitter->widget(0)->isVisible();
 }
 
-void MediaView::timerPlay() {
-    // Workaround Phonon bug on Mac OSX
-    // qDebug() << mediaObject->currentTime();
-    if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
-        // qDebug() << "Mac playback workaround";
-        mediaObject->pause();
-        // QTimer::singleShot(1000, mediaObject, SLOT(play()));
-        mediaObject->play();
-    }
-}
-
 void MediaView::saveSplitterState() {
     QSettings settings;
     settings.setValue("splitter", splitter->saveState());
@@ -695,7 +669,7 @@ void MediaView::updateContinueButton(int value) {
 #endif
 
 void MediaView::downloadVideo() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     DownloadManager::instance()->addItem(video);
     The::globalActions()->value("downloads")->setVisible(true);
@@ -740,7 +714,6 @@ void MediaView::sliderMoved(int value) {
 void MediaView::seekTo(int value) {
     qDebug() << __func__;
     mediaObject->pause();
-    workaroundTimer->stop();
     errorTimer->stop();
     // mediaObject->clear();
 
@@ -767,7 +740,7 @@ void MediaView::seekTo(int value) {
 void MediaView::findVideoParts() {
 
     // parts
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
 
     QString query = video->title();
@@ -822,7 +795,7 @@ void MediaView::findVideoParts() {
 }
 
 void MediaView::shareViaTwitter() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QUrl url("https://twitter.com/intent/tweet");
     url.addQueryItem("via", "minitubeapp");
@@ -832,7 +805,7 @@ void MediaView::shareViaTwitter() {
 }
 
 void MediaView::shareViaFacebook() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QUrl url("https://www.facebook.com/sharer.php");
     url.addQueryItem("t", video->title());
@@ -841,19 +814,18 @@ void MediaView::shareViaFacebook() {
 }
 
 void MediaView::shareViaBuffer() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QUrl url("http://bufferapp.com/add");
     url.addQueryItem("via", "minitubeapp");
     url.addQueryItem("text", video->title());
     url.addQueryItem("url", video->webpage().toString());
-    if (!video->thumbnailUrls().isEmpty())
-        url.addQueryItem("picture", video->thumbnailUrls().first().toString());
+    url.addQueryItem("picture", video->thumbnailUrl());
     QDesktopServices::openUrl(url);
 }
 
 void MediaView::shareViaEmail() {
-    Video* video = listModel->activeVideo();
+    Video* video = playlistModel->activeVideo();
     if (!video) return;
     QUrl url("mailto:");
     url.addQueryItem("subject", video->title());
@@ -866,7 +838,7 @@ void MediaView::shareViaEmail() {
 }
 
 void MediaView::authorPushed(QModelIndex index) {
-    Video* video = listModel->videoAt(index.row());
+    Video* video = playlistModel->videoAt(index.row());
     if (!video) return;
 
     QString channel = video->authorUri();
index 088b98cd01f74ebbc411301362cb364abb4398a3..3e10b56ec00a8320b30c9c426a0203837826d585 100644 (file)
@@ -7,35 +7,40 @@
 #include <phonon/videowidget.h>
 #include <phonon/seekslider.h>
 #include "view.h"
-#include "listmodel.h"
-#include "segmentedcontrol.h"
-#include "searchparams.h"
-#include "loadingwidget.h"
-#include "videoareawidget.h"
 
+class Video;
+class PlaylistModel;
+class SearchParams;
+class LoadingWidget;
+class VideoAreaWidget;
 class DownloadItem;
 class PlaylistView;
 class SidebarWidget;
+class VideoSource;
 
 namespace The {
     QMap<QString, QAction*>* globalActions();
 }
 
 class MediaView : public QWidget, public View {
+
     Q_OBJECT
 
 public:
-    MediaView(QWidget *parent);
+    static MediaView* instance();
     void initialize();
 
     void appear();
     void disappear();
 
     void setMediaObject(Phonon::MediaObject *mediaObject);
-    void setSlider(Phonon::SeekSlider *slider) { this->seekSlider = slider; }
+    const QList<VideoSource*> & getHistory() { return history; }
+    int getHistoryIndex();
+    PlaylistModel* getPlaylistModel() { return playlistModel; }
 
 public slots:
     void search(SearchParams *searchParams);
+    void setVideoSource(VideoSource *videoSource, bool addToHistory = true);
     void pause();
     void stop();
     void skip();
@@ -58,6 +63,10 @@ public slots:
     void snapshot();
     void fullscreen();
     void findVideoParts();
+    bool canGoBack();
+    void goBack();
+    bool canGoForward();
+    void goForward();
 
 private slots:
     // list/model
@@ -70,14 +79,7 @@ private slots:
     // phonon
     void stateChanged(Phonon::State newState, Phonon::State oldState);
     void currentSourceChanged(const Phonon::MediaSource source);
-    void showVideoContextMenu(QPoint point);
     void aboutToFinish();
-    // bar
-    void searchMostRelevant();
-    void searchMostRecent();
-    void searchMostViewed();
-    // timer
-    void timerPlay();
 #ifdef APP_ACTIVATION
     void demoMessage();
     void updateContinueButton(int);
@@ -96,36 +98,23 @@ private slots:
     */
 
 private:
+    MediaView(QWidget *parent = 0);
+    SearchParams* getSearchParams();
     static QRegExp wordRE(QString s);
 
-    SearchParams *searchParams;
-
     QSplitter *splitter;
-
     SidebarWidget *sidebar;
-    PlaylistView *listView;
-    ListModel *listModel;
-
-    // sortBar
-    SegmentedControl *sortBar;
-    QAction *mostRelevantAction;
-    QAction *mostRecentAction;
-    QAction *mostViewedAction;
+    PlaylistView *playlistView;
+    PlaylistModel *playlistModel;
+    VideoAreaWidget *videoAreaWidget;
+    LoadingWidget *loadingWidget;
 
     // phonon
     Phonon::MediaObject *mediaObject;
     Phonon::VideoWidget *videoWidget;
-    Phonon::SeekSlider *seekSlider;
 
-    // loadingWidget
-    VideoAreaWidget *videoAreaWidget;
-    LoadingWidget *loadingWidget;
-
-    bool timerPlayFlag;
     bool reallyStopped;
-
     QTimer *errorTimer;
-    QTimer *workaroundTimer;
     Video *skippedVideo;
 
 #ifdef APP_ACTIVATION
@@ -133,7 +122,7 @@ private:
 #endif
 
     DownloadItem *downloadItem;
-
+    QList<VideoSource*> history;
 };
 
 #endif // __MEDIAVIEW_H__
index 7ca439cc8f233c4bd05f8e86d24efb07a19f7124..6800f9962dc17bf26779900d8cc7b260a57bba9f 100644 (file)
@@ -1,13 +1,10 @@
 #include "playlistitemdelegate.h"
-#include "listmodel.h"
+#include "playlistmodel.h"
 #include "fontutils.h"
 #include "downloaditem.h"
 #include "iconloader/qticonloader.h"
 #include "videodefinition.h"
-
-#include <QFontMetricsF>
-#include <QPainter>
-#include <QHash>
+#include "video.h"
 
 const qreal PlaylistItemDelegate::THUMB_HEIGHT = 90.0;
 const qreal PlaylistItemDelegate::THUMB_WIDTH = 120.0;
@@ -98,23 +95,14 @@ void PlaylistItemDelegate::paintBody( QPainter* painter,
     const Video *video = videoPointer.data();
 
     // thumb
-    if (!video->thumbnail().isNull()) {
-        painter->drawImage(QRect(0, 0, THUMB_WIDTH, THUMB_HEIGHT), video->thumbnail());
-
-        // play icon overlayed on the thumb
-        if (isActive)
-            paintPlayIcon(painter);
-
-        // time
-        QString timeString;
-        int duration = video->duration();
-        if ( duration > 3600 )
-            timeString = QTime().addSecs(duration).toString("h:mm:ss");
-        else
-            timeString = QTime().addSecs(duration).toString("m:ss");
-        drawTime(painter, timeString, line);
+    painter->drawPixmap(0, 0, THUMB_WIDTH, THUMB_HEIGHT, video->thumbnail());
 
-    }
+    // play icon overlayed on the thumb
+    if (isActive)
+        paintPlayIcon(painter);
+
+    // time
+    drawTime(painter, video->formattedDuration(), line);
 
     if (isActive) painter->setFont(boldFont);
 
index 5f8399b4744670860658f21ee276581acd462c3b..5a7b2b326123c6b96de8ef5dfcb2a1c3116b1cb9 100644 (file)
@@ -1,11 +1,7 @@
-#ifndef PRETTYITEMDELEGATE_H
-#define PRETTYITEMDELEGATE_H
+#ifndef PLAYLISTITEMDELEGATE_H
+#define PLAYLISTITEMDELEGATE_H
 
-#include <QModelIndex>
-#include <QStyledItemDelegate>
-
-class QPainter;
-class QProgressBar;
+#include <QtGui>
 
 class PlaylistItemDelegate : public QStyledItemDelegate {
 
index 4e8662b822dbbd5a96bef271653e31b8350c1337..bdd0b4dcb0239d5dfff73d5c9c2148c75fc09a57 100644 (file)
@@ -1,28 +1,29 @@
-#include "listmodel.h"
+#include "playlistmodel.h"
 #include "videomimedata.h"
+#include "videosource.h"
+#include "ytsearch.h"
+#include "video.h"
+#include "searchparams.h"
 
-#define MAX_ITEMS 10
+static const int maxItems = 10;
 static const QString recentKeywordsKey = "recentKeywords";
 static const QString recentChannelsKey = "recentChannels";
 
-ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) {
-    youtubeSearch = 0;
+PlaylistModel::PlaylistModel(QWidget *parent) : QAbstractListModel(parent) {
+    videoSource = 0;
     searching = false;
     canSearchMore = true;
     m_activeVideo = 0;
     m_activeRow = -1;
     skip = 1;
+    max = 0;
 
     hoveredRow = -1;
     authorHovered = false;
     authorPressed = false;
 }
 
-ListModel::~ListModel() {
-    delete youtubeSearch;
-}
-
-int ListModel::rowCount(const QModelIndex &/*parent*/) const {
+int PlaylistModel::rowCount(const QModelIndex &/*parent*/) const {
     int count = videos.size();
     
     // add the message item
@@ -32,7 +33,7 @@ int ListModel::rowCount(const QModelIndex &/*parent*/) const {
     return count;
 }
 
-QVariant ListModel::data(const QModelIndex &index, int role) const {
+QVariant PlaylistModel::data(const QModelIndex &index, int role) const {
     
     int row = index.row();
     
@@ -49,7 +50,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const {
         case Qt::StatusTipRole:
             if (!errorMessage.isEmpty()) return errorMessage;
             if (searching) return tr("Searching...");
-            if (canSearchMore) return tr("Show %1 More").arg(MAX_ITEMS);
+            if (canSearchMore) return tr("Show %1 More").arg(maxItems);
             if (videos.isEmpty()) return tr("No videos");
             else return tr("No more videos");
         case Qt::TextAlignmentRole:
@@ -95,7 +96,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const {
     return QVariant();
 }
 
-void ListModel::setActiveRow( int row) {
+void PlaylistModel::setActiveRow( int row) {
     if ( rowExists( row ) ) {
         
         m_activeRow = row;
@@ -116,102 +117,99 @@ void ListModel::setActiveRow( int row) {
 
 }
 
-int ListModel::nextRow() const {
+int PlaylistModel::nextRow() const {
     int nextRow = m_activeRow + 1;
     if (rowExists(nextRow))
         return nextRow;
     return -1;
 }
 
-int ListModel::previousRow() const {
+int PlaylistModel::previousRow() const {
     int prevRow = m_activeRow - 1;
     if (rowExists(prevRow))
         return prevRow;
     return -1;
 }
 
-Video* ListModel::videoAt( int row ) const {
+Video* PlaylistModel::videoAt( int row ) const {
     if ( rowExists( row ) )
         return videos.at( row );
     return 0;
 }
 
-Video* ListModel::activeVideo() const {
+Video* PlaylistModel::activeVideo() const {
     return m_activeVideo;
 }
 
-void ListModel::search(SearchParams *searchParams) {
-
-    // delete current videos
+void PlaylistModel::setVideoSource(VideoSource *videoSource) {
     while (!videos.isEmpty())
         delete videos.takeFirst();
+
     m_activeVideo = 0;
     m_activeRow = -1;
     skip = 1;
-    errorMessage.clear();
     reset();
 
-    // (re)initialize the YouTubeSearch
-    if (youtubeSearch) delete youtubeSearch;
-    youtubeSearch = new YouTubeSearch();
-    connect(youtubeSearch, SIGNAL(gotVideo(Video*)), this, SLOT(addVideo(Video*)));
-    connect(youtubeSearch, SIGNAL(finished(int)), this, SLOT(searchFinished(int)));
-    connect(youtubeSearch, SIGNAL(error(QString)), this, SLOT(searchError(QString)));
+    this->videoSource = videoSource;
+    connect(videoSource, SIGNAL(gotVideo(Video*)),
+            SLOT(addVideo(Video*)), Qt::UniqueConnection);
+    connect(videoSource, SIGNAL(finished(int)),
+            SLOT(searchFinished(int)), Qt::UniqueConnection);
+    connect(videoSource, SIGNAL(error(QString)),
+            SLOT(searchError(QString)), Qt::UniqueConnection);
 
-    this->searchParams = searchParams;
-    searching = true;
-    youtubeSearch->search(searchParams, MAX_ITEMS, skip);
-    skip += MAX_ITEMS;
+    searchMore();
 }
 
-void ListModel::searchMore(int max) {
+void PlaylistModel::searchMore(int max) {
     if (searching) return;
     searching = true;
+    this->max = max;
     errorMessage.clear();
-    youtubeSearch->search(searchParams, max, skip);
+    videoSource->loadVideos(max, skip);
     skip += max;
 }
 
-void ListModel::searchMore() {
-    searchMore(MAX_ITEMS);
+void PlaylistModel::searchMore() {
+    searchMore(maxItems);
 }
 
-void ListModel::searchNeeded() {
+void PlaylistModel::searchNeeded() {
     int remainingRows = videos.size() - m_activeRow;
-    int rowsNeeded = MAX_ITEMS - remainingRows;
+    int rowsNeeded = maxItems - remainingRows;
     if (rowsNeeded > 0)
         searchMore(rowsNeeded);
 }
 
-void ListModel::abortSearch() {
+void PlaylistModel::abortSearch() {
     while (!videos.isEmpty())
         delete videos.takeFirst();
     reset();
-    youtubeSearch->abort();
+    videoSource->abort();
     searching = false;
 }
 
-void ListModel::searchFinished(int total) {
+void PlaylistModel::searchFinished(int total) {
     searching = false;
-    canSearchMore = total > 0;
+    canSearchMore = total >= max;
 
     // update the message item
-    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
+    emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
 
-    if (!youtubeSearch->getSuggestions().isEmpty()) {
-        emit haveSuggestions(youtubeSearch->getSuggestions());
-    }
+    if (!videoSource->getSuggestions().isEmpty())
+        emit haveSuggestions(videoSource->getSuggestions());
 }
 
-void ListModel::searchError(QString message) {
+void PlaylistModel::searchError(QString message) {
     errorMessage = message;
     // update the message item
-    emit dataChanged( createIndex( MAX_ITEMS, 0 ), createIndex( MAX_ITEMS, columnCount() - 1 ) );
+    emit dataChanged( createIndex( maxItems, 0 ), createIndex( maxItems, columnCount() - 1 ) );
 }
 
-void ListModel::addVideo(Video* video) {
+void PlaylistModel::addVideo(Video* video) {
     
-    connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
+    connect(video, SIGNAL(gotThumbnail()), SLOT(updateThumbnail()), Qt::UniqueConnection);
+    video->loadThumbnail();
 
     beginInsertRows(QModelIndex(), videos.size(), videos.size());
     videos << video;
@@ -225,38 +223,47 @@ void ListModel::addVideo(Video* video) {
         if (!settings.value("manualplay", false).toBool())
             setActiveRow(0);
 
-        // save keyword
-        QString query = searchParams->keywords();
-        if (!query.isEmpty() && !searchParams->isTransient()) {
-            if (query.startsWith("http://")) {
-                // Save the video title
-                query += "|" + videos.first()->title();
+        if (videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
+
+            static const int maxRecentElements = 10;
+
+            YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
+            SearchParams *searchParams = search->getSearchParams();
+
+            // save keyword
+            QString query = searchParams->keywords();
+            if (!query.isEmpty() && !searchParams->isTransient()) {
+                if (query.startsWith("http://")) {
+                    // Save the video title
+                    query += "|" + videos.first()->title();
+                }
+                QStringList keywords = settings.value(recentKeywordsKey).toStringList();
+                keywords.removeAll(query);
+                keywords.prepend(query);
+                while (keywords.size() > maxRecentElements)
+                    keywords.removeLast();
+                settings.setValue(recentKeywordsKey, keywords);
             }
-            QStringList keywords = settings.value(recentKeywordsKey).toStringList();
-            keywords.removeAll(query);
-            keywords.prepend(query);
-            while (keywords.size() > 10)
-                keywords.removeLast();
-            settings.setValue(recentKeywordsKey, keywords);
-        }
 
-        // save channel
-        QString channel = searchParams->author();
-        if (!channel.isEmpty() && !searchParams->isTransient()) {
-            QSettings settings;
-            QStringList channels = settings.value(recentChannelsKey).toStringList();
-            channels.removeAll(channel);
-            channels.prepend(channel);
-            while (channels.size() > 10)
-                channels.removeLast();
-            settings.setValue(recentChannelsKey, channels);
+            // save channel
+            QString channel = searchParams->author();
+            if (!channel.isEmpty() && !searchParams->isTransient()) {
+                if (!video->authorUri().isEmpty())
+                    channel = video->authorUri() + "|" + video->author();
+                QStringList channels = settings.value(recentChannelsKey).toStringList();
+                channels.removeAll(channel);
+                channels.prepend(channel);
+                while (channels.size() > maxRecentElements)
+                    channels.removeLast();
+                settings.setValue(recentChannelsKey, channels);
+            }
         }
 
     }
 
 }
 
-void ListModel::updateThumbnail() {
+void PlaylistModel::updateThumbnail() {
 
     Video *video = static_cast<Video *>(sender());
     if (!video) {
@@ -274,7 +281,7 @@ void ListModel::updateThumbnail() {
 /**
   * This function does not free memory
   */
-bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
+bool PlaylistModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
     beginRemoveRows(QModelIndex(), position, position+rows-1);
     for (int row = 0; row < rows; ++row) {
         videos.removeAt(position);
@@ -283,7 +290,7 @@ bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*
     return true;
 }
 
-void ListModel::removeIndexes(QModelIndexList &indexes) {
+void PlaylistModel::removeIndexes(QModelIndexList &indexes) {
     QList<Video*> originalList(videos);
     QList<Video*> delitems;
     foreach (QModelIndex index, indexes) {
@@ -306,26 +313,26 @@ void ListModel::removeIndexes(QModelIndexList &indexes) {
 
 
 
-Qt::DropActions ListModel::supportedDropActions() const {
+Qt::DropActions PlaylistModel::supportedDropActions() const {
     return Qt::MoveAction;
 }
 
-Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
+Qt::ItemFlags PlaylistModel::flags(const QModelIndex &index) const {
     if (index.isValid())
         if (index.row() == videos.size()) {
             // don't drag the "show 10 more" item
-            return Qt::ItemIsEnabled;
+            return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
         } else return (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
     return Qt::ItemIsDropEnabled;
 }
 
-QStringList ListModel::mimeTypes() const {
+QStringList PlaylistModel::mimeTypes() const {
     QStringList types;
     types << "application/x-minitube-video";
     return types;
 }
 
-QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
+QMimeData* PlaylistModel::mimeData( const QModelIndexList &indexes ) const {
     VideoMimeData* mime = new VideoMimeData();
 
     foreach( const QModelIndex &it, indexes ) {
@@ -337,9 +344,9 @@ QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
     return mime;
 }
 
-bool ListModel::dropMimeData(const QMimeData *data,
-                             Qt::DropAction action, int row, int column,
-                             const QModelIndex &parent) {
+bool PlaylistModel::dropMimeData(const QMimeData *data,
+                                 Qt::DropAction action, int row, int column,
+                                 const QModelIndex &parent) {
     if (action == Qt::IgnoreAction)
         return true;
 
@@ -384,15 +391,15 @@ bool ListModel::dropMimeData(const QMimeData *data,
 
 }
 
-int ListModel::rowForVideo(Video* video) {
+int PlaylistModel::rowForVideo(Video* video) {
     return videos.indexOf(video);
 }
 
-QModelIndex ListModel::indexForVideo(Video* video) {
+QModelIndex PlaylistModel::indexForVideo(Video* video) {
     return createIndex(videos.indexOf(video), 0);
 }
 
-void ListModel::move(QModelIndexList &indexes, bool up) {
+void PlaylistModel::move(QModelIndexList &indexes, bool up) {
     QList<Video*> movedVideos;
 
     foreach (QModelIndex index, indexes) {
@@ -426,45 +433,45 @@ void ListModel::move(QModelIndexList &indexes, bool up) {
 
 /* row hovering */
 
-void ListModel::setHoveredRow(int row) {
+void PlaylistModel::setHoveredRow(int row) {
     int oldRow = hoveredRow;
     hoveredRow = row;
     emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
 }
 
-void ListModel::clearHover() {
+void PlaylistModel::clearHover() {
     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
     hoveredRow = -1;
 }
 
 /* clickable author */
 
-void ListModel::enterAuthorHover() {
+void PlaylistModel::enterAuthorHover() {
     if (authorHovered) return;
     authorHovered = true;
     updateAuthor();
 }
 
-void ListModel::exitAuthorHover() {
+void PlaylistModel::exitAuthorHover() {
     if (!authorHovered) return;
     authorHovered = false;
     updateAuthor();
     setHoveredRow(hoveredRow);
 }
 
-void ListModel::enterAuthorPressed() {
+void PlaylistModel::enterAuthorPressed() {
     if (authorPressed) return;
     authorPressed = true;
     updateAuthor();
 }
 
-void ListModel::exitAuthorPressed() {
+void PlaylistModel::exitAuthorPressed() {
     if (!authorPressed) return;
     authorPressed = false;
     updateAuthor();
 }
 
-void ListModel::updateAuthor() {
+void PlaylistModel::updateAuthor() {
     emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
 }
index f9d424bec16aa4627420cdda9d9a0a8581fe9da1..1a1be5cfb23b9f262c0d7316af69a595a640eced 100644 (file)
@@ -1,9 +1,10 @@
-#ifndef LISTMODEL_H
-#define LISTMODEL_H
+#ifndef PLAYLISTMODEL_H
+#define PLAYLISTMODEL_H
 
-#include "video.h"
-#include "youtubesearch.h"
-#include "searchparams.h"
+#include <QtGui>
+
+class Video;
+class VideoSource;
 
 enum DataRoles {
     ItemTypeRole = Qt::UserRole,
@@ -22,19 +23,15 @@ enum ItemTypes {
     ItemTypeShowMore
 };
 
-class ListModel : public QAbstractListModel {
+class PlaylistModel : public QAbstractListModel {
 
     Q_OBJECT
 
 public:
+    PlaylistModel(QWidget *parent = 0);
 
-    ListModel(QWidget *parent);
-    ~ListModel();
-
-    // inherited from QAbstractListModel
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    // int rowCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return videos.size(); }
-    int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 4; }
+    int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 1; }
     QVariant data(const QModelIndex &index, int role) const;
     bool removeRows(int position, int rows, const QModelIndex &parent);
 
@@ -46,7 +43,6 @@ public:
                       Qt::DropAction action, int row, int column,
                       const QModelIndex &parent);
 
-    // custom methods
     void setActiveRow( int row );
     bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); }
     int activeRow() const { return m_activeRow; } // returns -1 if there is no active row
@@ -60,11 +56,10 @@ public:
     Video* videoAt( int row ) const;
     Video* activeVideo() const;
 
-    // video search methods
-    void search(SearchParams *searchParams);
+    VideoSource* getVideoSource() { return videoSource; }
+    void setVideoSource(VideoSource *videoSource);
     void abortSearch();
 
-
 public slots:
     void searchMore();
     void searchNeeded();
@@ -89,15 +84,14 @@ signals:
 private:
     void searchMore(int max);
 
-    YouTubeSearch *youtubeSearch;
-    SearchParams *searchParams;
+    VideoSource *videoSource;
     bool searching;
     bool canSearchMore;
 
     QList<Video*> videos;
     int skip;
+    int max;
 
-    // the row being played
     int m_activeRow;
     Video *m_activeVideo;
 
diff --git a/src/playlistsuggest.cpp b/src/playlistsuggest.cpp
new file mode 100644 (file)
index 0000000..26df116
--- /dev/null
@@ -0,0 +1,121 @@
+#include "playlistsuggest.h"
+#include <QtXml>
+#include "networkaccess.h"
+
+namespace The {
+NetworkAccess* http();
+}
+
+struct Playlist {
+    QString id;
+    QString title;
+    QString summary;
+    QString author;
+    int videoCount;
+};
+
+PlaylistSuggest::PlaylistSuggest(QObject *parent) : Suggester() {
+
+}
+
+void PlaylistSuggest::suggest(QString query) {
+    QUrl url("http://gdata.youtube.com/feeds/api/playlists/snippets");
+    url.addQueryItem("v", "2");
+    url.addQueryItem("q", query);
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(handleNetworkData(QByteArray)));
+}
+
+void PlaylistSuggest::handleNetworkData(QByteArray data) {
+    QList<Playlist> playlists;
+
+    QXmlStreamReader xml(data);
+    while (!xml.atEnd()) {
+        xml.readNext();
+        if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == "entry") {
+
+            Playlist playlist = {};
+
+            while (xml.readNextStartElement()) {
+                if (xml.name() == "title") {
+                    playlist.title = xml.readElementText();
+                }
+                else if (xml.name() == "summary") {
+                    playlist.summary = xml.readElementText();
+                }
+                else if (xml.name() == "author") {
+                    while (xml.readNextStartElement()) {
+                        if (xml.name() == "name") {
+                            playlist.author = xml.readElementText();
+                            break;
+                        }
+                    }
+                }
+                else if (xml.name() == "playlistId") {
+                    playlist.id = xml.readElementText();
+                }
+                else if (xml.name() == "countHint") {
+                    playlist.videoCount = xml.readElementText().toInt();
+                }
+            }
+
+            playlists << playlist;
+
+        }
+    }
+
+    // emit ready(choices);
+}
+
+/* model */
+
+class PlaylistSuggestModel : public QAbstractListModel {
+
+    Q_OBJECT
+
+public:
+    PlaylistSuggestModel(QWidget *parent) : QAbstractListModel(parent) { }
+    int rowCount(const QModelIndex &parent = QModelIndex()) const {
+        Q_UNUSED(parent);
+        return list.size();
+    }
+    int columnCount( const QModelIndex& parent = QModelIndex() ) const {
+        Q_UNUSED(parent);
+        return 1;
+    }
+    QVariant data(const QModelIndex &index, int role) const {
+        Q_UNUSED(index);
+        Q_UNUSED(role);
+        return QVariant();
+    }
+    QList<Playlist> list;
+
+};
+
+/* delegate */
+
+class PlaylistSuggestDelegate : public QStyledItemDelegate {
+
+    Q_OBJECT
+
+public:
+    PlaylistSuggestDelegate(QObject* parent);
+    QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const {
+        return QSize(0, 100);
+    }
+    void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+        QStyleOptionViewItemV4 opt = QStyleOptionViewItemV4(option);
+        initStyleOption(&opt, index);
+        opt.text = "";
+        opt.widget->style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
+
+        painter->save();
+        painter->translate(option.rect.topLeft());
+
+        QRect line(0, 0, option.rect.width(), option.rect.height());
+
+        painter->restore();
+
+    }
+
+};
diff --git a/src/playlistsuggest.h b/src/playlistsuggest.h
new file mode 100644 (file)
index 0000000..c8f455b
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef PLAYLISTSUGGEST_H
+#define PLAYLISTSUGGEST_H
+
+#include <QtGui>
+#include "suggester.h"
+
+class PlaylistSuggest : public Suggester {
+
+    Q_OBJECT
+
+public:
+    PlaylistSuggest(QObject *parent = 0);
+    void suggest(QString query);
+
+signals:
+    void ready(QStringList);
+
+private slots:
+    void handleNetworkData(QByteArray response);
+
+};
+
+#endif // PLAYLISTSUGGEST_H
index d73fd39f9ee5a0b2216eb522771a5727fe6f5eb1..6a7d5b963ddcff9b7549ddebb1711597602ff0b1 100644 (file)
@@ -1,63 +1,92 @@
 #include "playlistview.h"
-#include "listmodel.h"
+#include "playlistmodel.h"
 #include "playlistitemdelegate.h"
 
 PlaylistView::PlaylistView(QWidget *parent) : QListView(parent) {
-    connect(this, SIGNAL(entered(const QModelIndex &)), SLOT(itemEntered(const QModelIndex &)));
+    clickableAuthors = true;
+
+    setItemDelegate(new PlaylistItemDelegate(this));
+    setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+    // dragndrop
+    setDragEnabled(true);
+    setAcceptDrops(true);
+    setDropIndicatorShown(true);
+    setDragDropMode(QAbstractItemView::DragDrop);
+
+    // cosmetics
+    setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+    setFrameShape(QFrame::NoFrame);
+    setAttribute(Qt::WA_MacShowFocusRect, false);
+    setMinimumSize(320, 240);
+    setUniformItemSizes(true);
+
+    connect(this, SIGNAL(entered(const QModelIndex &)),
+            SLOT(itemEntered(const QModelIndex &)));
     setMouseTracking(true);
 }
 
 void PlaylistView::itemEntered(const QModelIndex &index) {
-    ListModel *listModel = dynamic_cast<ListModel *>(model());
+    PlaylistModel *listModel = dynamic_cast<PlaylistModel *>(model());
     if (listModel) listModel->setHoveredRow(index.row());
 }
 
 void PlaylistView::leaveEvent(QEvent * /* event */) {
-    ListModel *listModel = dynamic_cast<ListModel *>(model());
+    PlaylistModel *listModel = dynamic_cast<PlaylistModel *>(model());
     if (listModel) listModel->clearHover();
 }
 
 void PlaylistView::mouseMoveEvent(QMouseEvent *event) {
     QListView::mouseMoveEvent(event);
-    QWidget::mouseMoveEvent(event);
-
-    if (isHoveringAuthor(event)) {
-
-        // check for special "message" item
-        ListModel *listModel = dynamic_cast<ListModel *>(model());
-        if (listModel && listModel->rowCount() == indexAt(event->pos()).row())
-            return;
+    // QWidget::mouseMoveEvent(event);
 
+    if (isHoveringThumbnail(event)) {
+        setCursor(Qt::PointingHandCursor);
+    } else if (isShowMoreItem(indexAt(event->pos()))) {
+        setCursor(Qt::PointingHandCursor);
+    } else if (isHoveringAuthor(event)) {
         QMetaObject::invokeMethod(model(), "enterAuthorHover");
         setCursor(Qt::PointingHandCursor);
     } else {
         QMetaObject::invokeMethod(model(), "exitAuthorHover");
         unsetCursor();
     }
-
 }
 
 void PlaylistView::mousePressEvent(QMouseEvent *event) {
-    if (event->button() == Qt::LeftButton
-        && isHoveringAuthor(event)) {
-        QMetaObject::invokeMethod(model(), "enterAuthorPressed");
-        event->ignore();
-    } else {
-        QListView::mousePressEvent(event);
-    }
+    if (event->button() == Qt::LeftButton) {
+        if (isHoveringThumbnail(event)) {
+            event->accept();
+        } else if (isHoveringAuthor(event)) {
+            QMetaObject::invokeMethod(model(), "enterAuthorPressed");
+            event->ignore();
+        } else QListView::mousePressEvent(event);
+    } else QListView::mousePressEvent(event);
 }
 
 void PlaylistView::mouseReleaseEvent(QMouseEvent *event) {
     if (event->button() == Qt::LeftButton) {
         QMetaObject::invokeMethod(model(), "exitAuthorPressed");
-        if (isHoveringAuthor(event))
-            emit authorPushed(indexAt(event->pos()));
+        const QModelIndex index =  indexAt(event->pos());
+        if (isHoveringThumbnail(event)) {
+            emit activated(index);
+            unsetCursor();
+        } else if (isHoveringAuthor(event)) {
+            emit authorPushed(index);
+        } else if (isShowMoreItem(index)) {
+            PlaylistModel *listModel = dynamic_cast<PlaylistModel *>(model());
+            listModel->searchMore();
+            unsetCursor();
+        }
+
     } else {
         QListView::mousePressEvent(event);
     }
 }
 
 bool PlaylistView::isHoveringAuthor(QMouseEvent *event) {
+    if (!clickableAuthors) return false;
+
     const QModelIndex itemIndex = indexAt(event->pos());
     const QRect itemRect = visualRect(itemIndex);
     // qDebug() << " itemRect.x()" <<  itemRect.x();
@@ -73,3 +102,17 @@ bool PlaylistView::isHoveringAuthor(QMouseEvent *event) {
 
     return ret;
 }
+
+bool PlaylistView::isHoveringThumbnail(QMouseEvent *event) {
+    const QModelIndex index = indexAt(event->pos());
+    const QRect itemRect = visualRect(index);
+    QRect thumbRect(0, 0, 120, 90);
+    const int x = event->x() - itemRect.x() - thumbRect.x();
+    const int y = event->y() - itemRect.y() - thumbRect.y();
+    return x > 0 && x < thumbRect.width() && y > 0 && y < thumbRect.height();
+}
+
+bool PlaylistView::isShowMoreItem(const QModelIndex &index) {
+    return model()->rowCount() > 1 &&
+            model()->rowCount() == index.row() + 1;
+}
index b058e8ba7e175ae4a35ccc6532d944e286390363..fb3bd0cd548948f9f2310253f74dd2fca03b418b 100644 (file)
@@ -9,13 +9,13 @@ class PlaylistView : public QListView {
 
 public:
     PlaylistView(QWidget *parent = 0);
+    void setClickableAuthors(bool enabled) { clickableAuthors = enabled; }
 
 protected:
     void leaveEvent(QEvent *event);
     void mouseMoveEvent(QMouseEvent *event);
     void mousePressEvent(QMouseEvent *event);
     void mouseReleaseEvent(QMouseEvent *event);
-    bool isHoveringAuthor(QMouseEvent *event);
 
 signals:
     void authorPushed(QModelIndex index);
@@ -23,6 +23,13 @@ signals:
 private slots:
     void itemEntered(const QModelIndex &index);
 
+private:
+    bool isHoveringAuthor(QMouseEvent *event);
+    bool isShowMoreItem(const QModelIndex &index);
+    bool isHoveringThumbnail(QMouseEvent *event);
+
+    bool clickableAuthors;
+
 };
 
 #endif // PLAYLISTVIEW_H
diff --git a/src/regionsview.cpp b/src/regionsview.cpp
new file mode 100644 (file)
index 0000000..c83ed92
--- /dev/null
@@ -0,0 +1,69 @@
+#include "regionsview.h"
+#include "ytregions.h"
+#include "mainwindow.h"
+
+RegionsView::RegionsView(QWidget *parent) : QWidget(parent) {
+    QBoxLayout *l = new QVBoxLayout(this);
+    l->setMargin(30);
+    l->setSpacing(30);
+
+    layout = new QGridLayout();
+    layout->setMargin(0);
+    layout->setSpacing(0);
+    l->addLayout(layout);
+
+    addRegion(YTRegions::worldwideRegion());
+    foreach(YTRegion region, YTRegions::list())
+        addRegion(region);
+
+    doneButton = new QPushButton(tr("Done"));
+    doneButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+    doneButton->setDefault(true);
+    doneButton->setProperty("custom", true);
+    doneButton->setProperty("important", true);
+    doneButton->setProperty("big", true);
+    connect(doneButton, SIGNAL(clicked()), MainWindow::instance(), SLOT(goBack()));
+    l->addWidget(doneButton, 0, Qt::AlignCenter);
+}
+
+void RegionsView::addRegion(const YTRegion &region) {
+    QPushButton *button = new QPushButton(region.name);
+    button->setProperty("regionId", region.id);
+    button->setCheckable(true);
+    button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+    button->setFocusPolicy(Qt::StrongFocus);
+    button->setIcon(YTRegions::iconForRegionId(region.id));
+    connect(button, SIGNAL(clicked()), SLOT(buttonClicked()));
+    const int i = layout->count();
+    static const int rows = 10;
+    layout->addWidget(button, i % rows, i / rows);
+}
+
+void RegionsView::appear() {
+    doneButton->setFocus();
+
+    QString currentRegionId = YTRegions::currentRegionId();
+    for (int i = 0; i < layout->count(); i++) {
+        QLayoutItem *item = layout->itemAt(i);
+        QPushButton *b = static_cast<QPushButton*>(item->widget());
+        QString regionId = b->property("regionId").toString();
+        b->setChecked(currentRegionId == regionId);
+    }
+}
+
+void RegionsView::buttonClicked() {
+    QObject* o = sender();
+    QString regionId = o->property("regionId").toString();
+    YTRegions::setRegion(regionId);
+    emit regionChanged();
+    doneButton->click();
+
+    // uncheck other buttons
+    /*
+    for (int i = 0; i < layout->count(); i++) {
+        QLayoutItem *item = layout->itemAt(i);
+        QPushButton *b = static_cast<QPushButton*>(item->widget());
+        if (b != o && b->isChecked()) b->setChecked(false);
+    }
+    */
+}
diff --git a/src/regionsview.h b/src/regionsview.h
new file mode 100644 (file)
index 0000000..cf92d20
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef REGIONSVIEW_H
+#define REGIONSVIEW_H
+
+#include <QtGui>
+#include "view.h"
+
+class YTRegion;
+
+class RegionsView : public QWidget, public View {
+
+    Q_OBJECT
+
+public:
+    RegionsView(QWidget *parent = 0);
+    void appear();
+
+signals:
+    void regionChanged();
+
+private slots:
+    void buttonClicked();
+
+private:
+    void addRegion(const YTRegion &region);
+    QGridLayout *layout;
+    QPushButton *doneButton;
+
+};
+
+#endif // REGIONSVIEW_H
index 5942a0caa893ff5caa8070908c09acdecf9d5ba7..a7906fc11fea1c5f4d716dd4e1dfc3d7aaf89429 100644 (file)
@@ -1,6 +1,6 @@
 #include "searchparams.h"
 
-SearchParams::SearchParams() {
+SearchParams::SearchParams(QObject *parent) : QObject(parent) {
     m_transient = false;
     m_sortBy = SortByRelevance;
     m_duration = DurationAny;
index 9af3f40e64c18f5571925963acb229d11e1d0cb2..24e023fa82f79dcdec10fd9655d41cb3d36ac366 100644 (file)
@@ -39,7 +39,7 @@ public:
         TimeMonth
     };
 
-    SearchParams();
+    SearchParams(QObject *parent = 0);
 
     const QString keywords() const { return m_keywords; }
     void setKeywords( QString keywords ) { m_keywords = keywords; }
@@ -62,6 +62,11 @@ public:
     int time() const { return m_time; }
     void setTime( int time ) { m_time = time; }
 
+    bool operator==(const SearchParams &other) const {
+        return m_keywords == other.keywords() &&
+                m_author == other.author();
+    }
+
 public slots:
     void setParam(QString name, QVariant value);
 
index ee6e97280c28eec391ba026d0ec6e88c31af57ff..14afc0a7d9104865ce5d9ae7adf2d1bdbd66ecbc 100644 (file)
@@ -2,7 +2,7 @@
 #include "constants.h"
 #include "fontutils.h"
 #include "searchparams.h"
-#include "youtubesuggest.h"
+#include "ytsuggester.h"
 #include "channelsuggest.h"
 #ifdef APP_MAC
 #include "searchlineedit_mac.h"
@@ -110,7 +110,7 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
     connect(queryEdit, SIGNAL(textChanged(const QString &)), SLOT(textChanged(const QString &)));
     connect(queryEdit, SIGNAL(suggestionAccepted(const QString&)), SLOT(watch(const QString&)));
 
-    youtubeSuggest = new YouTubeSuggest(this);
+    youtubeSuggest = new YTSuggester(this);
     channelSuggest = new ChannelSuggest(this);
     searchTypeChanged(0);
 
@@ -240,12 +240,10 @@ void SearchView::updateRecentChannels() {
     foreach (QString keyword, keywords) {
         QString link = keyword;
         QString display = keyword;
-        if (keyword.startsWith("http://") || keyword.startsWith("https://")) {
-            int separator = keyword.indexOf("|");
-            if (separator > 0 && separator + 1 < keyword.length()) {
-                link = keyword.left(separator);
-                display = keyword.mid(separator+1);
-            }
+        int separator = keyword.indexOf('|');
+        if (separator > 0 && separator + 1 < keyword.length()) {
+            link = keyword.left(separator);
+            display = keyword.mid(separator+1);
         }
         QLabel *itemLabel = new QLabel("<a href=\"" + link
                                        + "\" style=\"color:palette(text); text-decoration:none\">"
index a62390d269b12e4350110b2b2445b735b5e4ee0e..ae1ad5266e6e2cf30ee0e02f711ae55c61553d0f 100644 (file)
@@ -6,7 +6,7 @@
 
 class SearchLineEdit;
 class SearchParams;
-class YouTubeSuggest;
+class YTSuggester;
 class ChannelSuggest;
 
 class SearchView : public QWidget, public View {
@@ -14,7 +14,7 @@ class SearchView : public QWidget, public View {
     Q_OBJECT
 
 public:
-    SearchView(QWidget *parent);
+    SearchView(QWidget *parent = 0);
     void updateRecentKeywords();
     void updateRecentChannels();
     QHash<QString, QVariant> metadata() {
@@ -25,6 +25,7 @@ public:
 
 public slots:
     void appear();
+    void disappear() { }
     void watch(QString query);
     void watchChannel(QString channel);
     void watchKeywords(QString query);
@@ -41,7 +42,7 @@ private slots:
     void searchTypeChanged(int index);
 
 private:
-    YouTubeSuggest *youtubeSuggest;
+    YTSuggester *youtubeSuggest;
     ChannelSuggest *channelSuggest;
 
     QComboBox *typeCombo;
index 418d3c8287241e805969a0971825f6c70ed9d88f..a896fbd0517f6af9d6405c8d103b08f8f6075cff 100644 (file)
@@ -128,8 +128,8 @@ void SegmentedControl::mouseReleaseEvent(QMouseEvent *event) {
 
 void SegmentedControl::leaveEvent(QEvent *event) {
     QWidget::leaveEvent(event);
-    // status tip    
-    MainWindow::instance()->statusBar()->clearMessage();
+    // status tip
+    // static_cast<QMainWindow*>(window())->statusBar()->clearMessage();
     d->hoveredAction = 0;
     d->pressedAction = 0;
     update();
diff --git a/src/sidebarheader.cpp b/src/sidebarheader.cpp
new file mode 100644 (file)
index 0000000..fb7dce6
--- /dev/null
@@ -0,0 +1,100 @@
+#include "sidebarheader.h"
+#include "iconloader/qticonloader.h"
+#include "mediaview.h"
+#include "videosource.h"
+#include "fontutils.h"
+
+SidebarHeader::SidebarHeader(QWidget *parent) : QToolBar(parent) { }
+
+void SidebarHeader::setup() {
+    static bool isSetup = false;
+    if (isSetup) return;
+    isSetup = true;
+
+    backAction = new QAction(
+                QtIconLoader::icon("go-previous"),
+                tr("&Back"), this);
+    connect(backAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goBack()));
+    addAction(backAction);
+
+    forwardAction = new QAction(
+                QtIconLoader::icon("go-next"),
+                tr("&Back"), this);
+    connect(forwardAction, SIGNAL(triggered()), MediaView::instance(), SLOT(goForward()));
+    addAction(forwardAction);
+
+    /*
+    QWidget *spacerWidget = new QWidget(this);
+    spacerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+    spacerWidget->setVisible(true);
+    addWidget(spacerWidget);
+    */
+}
+
+QSize SidebarHeader::minimumSizeHint (void) const {
+    return(QSize(1, QFontMetrics(font()).height() * 1.9));
+}
+
+void SidebarHeader::updateInfo() {
+    setup();
+
+    QList<VideoSource*> history = MediaView::instance()->getHistory();
+    int currentIndex = MediaView::instance()->getHistoryIndex();
+
+    bool canGoForward = MediaView::instance()->canGoForward();
+    forwardAction->setVisible(canGoForward);
+    if (canGoForward) {
+        VideoSource *nextVideoSource = history.at(currentIndex + 1);
+        forwardAction->setStatusTip(
+                    tr("Forward to %1")
+                    .arg(nextVideoSource->getName()));
+    }
+
+    bool canGoBack = MediaView::instance()->canGoBack();
+    bool backVisible = canGoForward || canGoBack;
+    backAction->setVisible(backVisible);
+    backAction->setEnabled(canGoBack);
+    if (canGoBack) {
+        VideoSource *previousVideoSource = history.at(currentIndex - 1);
+        backAction->setStatusTip(
+                    tr("Back to %1")
+                    .arg(previousVideoSource->getName()));
+    }
+
+    VideoSource *currentVideoSource = history.at(currentIndex);
+    connect(currentVideoSource, SIGNAL(nameChanged(QString)),
+            SLOT(updateTitle(QString)), Qt::UniqueConnection);
+    setTitle(currentVideoSource->getName());
+}
+
+void SidebarHeader::updateTitle(QString title) {
+    sender()->disconnect(this);
+    setTitle(title);
+}
+
+void SidebarHeader::setTitle(QString title) {
+    this->title = title;
+    update();
+}
+
+void SidebarHeader::paintEvent(QPaintEvent *event) {
+    QToolBar::paintEvent(event);
+    if (title.isEmpty()) return;
+    QPainter p(this);
+    p.setFont(FontUtils::smallBold());
+    p.setPen(Qt::white);
+
+    const QRect r = rect();
+
+    QString t = title;
+    QRect textBox = p.boundingRect(r, Qt::AlignCenter, t);
+    int i = 1;
+    static const int margin = 100;
+    while (textBox.width() > r.width() - margin) {
+        t = t.left(t.length() - i) + "...";
+        textBox = p.boundingRect(r, Qt::AlignCenter, t);
+        i++;
+    }
+
+    p.drawText(r, Qt::AlignCenter, t);
+}
diff --git a/src/sidebarheader.h b/src/sidebarheader.h
new file mode 100644 (file)
index 0000000..1ef4937
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef SIDEBARHEADER_H
+#define SIDEBARHEADER_H
+
+#include <QtGui>
+
+class SidebarHeader : public QToolBar {
+
+    Q_OBJECT
+
+public:
+    SidebarHeader(QWidget *parent = 0);
+    void updateInfo();
+
+protected:
+    QSize minimumSizeHint() const;
+    void paintEvent(QPaintEvent *event);
+
+private slots:
+    void updateTitle(QString title);
+
+private:
+    void setup();
+    void setTitle(QString title);
+
+    QAction *backAction;
+    QAction * forwardAction;
+    QString title;
+};
+
+#endif // SIDEBARHEADER_H
index ceae1503a2c06b0cdaf571fac3aa048129c4201e..ceccad73cc383500339b6ecd02473f94ef6c8d97 100644 (file)
@@ -1,6 +1,7 @@
 #include "sidebarwidget.h"
 #include "refinesearchbutton.h"
 #include "refinesearchwidget.h"
+#include "sidebarheader.h"
 #ifndef Q_WS_X11
 #include "extra.h"
 #endif
@@ -14,9 +15,12 @@ SidebarWidget::SidebarWidget(QWidget *parent) :
     playlist = 0;
 
     QBoxLayout *layout = new QVBoxLayout(this);
-    layout->setSpacing(1);
+    layout->setSpacing(0);
     layout->setMargin(0);
 
+    sidebarHeader = new SidebarHeader();
+    layout->addWidget(sidebarHeader);
+
     // hidden message widget
     messageLabel = new QLabel(this);
     messageLabel->setMargin(10);
@@ -70,9 +74,11 @@ void SidebarWidget::setPlaylist(QListView *playlist) {
 void SidebarWidget::showPlaylist() {
     setup();
     stackedWidget->setCurrentWidget(playlist);
+    The::globalActions()->value("refine-search")->setChecked(false);
 }
 
 void SidebarWidget::showRefineSearchWidget() {
+    if (!refineSearchWidget->isEnabled()) return;
     refineSearchWidget->setDirty(false);
     stackedWidget->setCurrentWidget(refineSearchWidget);
     refineSearchWidget->setFocus();
@@ -131,6 +137,7 @@ void SidebarWidget::handleMouseMove() {
 }
 
 void SidebarWidget::showRefineSearchButton() {
+    if (!refineSearchWidget->isEnabled()) return;
     refineSearchButton->move(
                 playlist->viewport()->width() - refineSearchButton->minimumWidth(),
                 height() - refineSearchButton->minimumHeight());
index 5edb56f81f5bd4911404c7f39388d11b2b07bde9..26d9bf0c173c457cc87090ced5524bb11bd59886 100644 (file)
@@ -5,6 +5,7 @@
 
 class RefineSearchButton;
 class RefineSearchWidget;
+class SidebarHeader;
 
 class SidebarWidget : public QWidget {
 
@@ -15,6 +16,7 @@ public:
     void setPlaylist(QListView *playlist);
     void showPlaylist();
     RefineSearchWidget* getRefineSearchWidget() { return refineSearchWidget; }
+    SidebarHeader* getHeader() { return sidebarHeader; }
     void hideSuggestions();
 
 public slots:
@@ -44,7 +46,7 @@ private:
     RefineSearchWidget *refineSearchWidget;
     QTimer *mouseTimer;
     QLabel *messageLabel;
-    
+    SidebarHeader *sidebarHeader;
 };
 
 #endif // SIDEBARWIDGET_H
diff --git a/src/standardfeedsview.cpp b/src/standardfeedsview.cpp
new file mode 100644 (file)
index 0000000..f6fedd7
--- /dev/null
@@ -0,0 +1,124 @@
+#include "standardfeedsview.h"
+#include "videosourcewidget.h"
+#include "ytcategories.h"
+#include "ytstandardfeed.h"
+#include "ytregions.h"
+#include "mainwindow.h"
+
+namespace The {
+QMap<QString, QAction*>* globalActions();
+}
+
+static const int cols = 5;
+
+StandardFeedsView::StandardFeedsView(QWidget *parent) : QWidget(parent),
+    layout(0) {
+    QPalette p = palette();
+    p.setBrush(QPalette::Window, Qt::black);
+    setPalette(p);
+    setAutoFillBackground(true);
+
+    connect(The::globalActions()->value("worldwide-region"), SIGNAL(triggered()),
+            SLOT(selectWorldwideRegion()));
+
+    connect(The::globalActions()->value("local-region"), SIGNAL(triggered()),
+            SLOT(selectLocalRegion()));
+
+    /*
+    QAction *regionAction = MainWindow::instance()->getRegionAction();
+    connect(regionAction, SIGNAL(changed()), SLOT(load()));
+    */
+}
+
+void StandardFeedsView::load() {
+    YTCategories *youTubeCategories = new YTCategories(this);
+    connect(youTubeCategories, SIGNAL(categoriesLoaded(const QList<YTCategory> &)),
+            SLOT(layoutCategories(const QList<YTCategory> &)));
+    youTubeCategories->loadCategories();
+
+    if (layout) {
+        while (QLayoutItem *item = layout->takeAt(0)) {
+            delete item->widget();
+            delete item;
+        }
+        delete layout;
+    }
+
+    layout = new QGridLayout(this);
+    layout->setMargin(0);
+    layout->setSpacing(1);
+
+    QList<YTStandardFeed*> feeds = getMainFeeds();
+    foreach(YTStandardFeed *feed, feeds)
+        addVideoSourceWidget(feed);
+
+    YTRegion region = YTRegions::currentRegion();
+    QToolButton *regionButton = MainWindow::instance()->getRegionButton();
+    regionButton->setText(region.name);
+    regionButton->setIcon(YTRegions::iconForRegionId(region.id));
+}
+
+void StandardFeedsView::layoutCategories(const QList<YTCategory> &categories) {
+    QString regionId = YTRegions::currentRegionId();
+    foreach(YTCategory category, categories) {
+        // assign a parent to this VideoSource  so it won't be deleted by MediaView
+        YTStandardFeed *feed = new YTStandardFeed(this);
+        feed->setCategory(category.term);
+        feed->setLabel(category.label);
+        feed->setRegionId(regionId);
+        feed->setFeedId("most_popular");
+        addVideoSourceWidget(feed);
+    }
+}
+
+void StandardFeedsView::addVideoSourceWidget(VideoSource *videoSource) {
+    VideoSourceWidget *w = new VideoSourceWidget(videoSource);
+    connect(w, SIGNAL(activated(VideoSource*)),
+            SIGNAL(activated(VideoSource*)));
+    int i = layout->count();
+    layout->addWidget(w, i / cols, i % cols);
+}
+
+QList<YTStandardFeed*> StandardFeedsView::getMainFeeds() {
+    QList<YTStandardFeed*> feeds;
+
+    feeds << buildStardardFeed("most_popular", tr("Most Popular"))
+          << buildStardardFeed("recently_featured", tr("Featured"))
+          << buildStardardFeed("most_shared", tr("Most Shared"))
+          << buildStardardFeed("most_discussed", tr("Most Discussed"))
+          << buildStardardFeed("top_rated", tr("Top Rated"));
+
+    return feeds;
+}
+
+YTStandardFeed* StandardFeedsView::buildStardardFeed(QString feedId, QString label) {
+    YTStandardFeed *feed = new YTStandardFeed(this);
+    feed->setFeedId(feedId);
+    feed->setLabel(label);
+    feed->setRegionId(YTRegions::currentRegionId());
+    return feed;
+}
+
+void StandardFeedsView::appear() {
+    setFocus();
+    if (!layout) load();
+    QAction *regionAction = MainWindow::instance()->getRegionAction();
+    regionAction->setVisible(true);
+}
+
+void StandardFeedsView::disappear() {
+    QAction *regionAction = MainWindow::instance()->getRegionAction();
+    regionAction->setVisible(false);
+}
+
+void StandardFeedsView::selectWorldwideRegion() {
+    YTRegions::setRegion(YTRegions::worldwideRegion().id);
+    load();
+}
+
+void StandardFeedsView::selectLocalRegion() {
+    YTRegions::setRegion(YTRegions::localRegion().id);
+    load();
+}
+
+
diff --git a/src/standardfeedsview.h b/src/standardfeedsview.h
new file mode 100644 (file)
index 0000000..17d850f
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef CATEGORIESVIEW_H
+#define CATEGORIESVIEW_H
+
+#include <QtGui>
+#include "view.h"
+
+class VideoSource;
+class YTCategory;
+class YTStandardFeed;
+
+class StandardFeedsView : public QWidget, public View {
+
+    Q_OBJECT
+
+public:
+    StandardFeedsView(QWidget *parent = 0);
+
+signals:
+    void activated(VideoSource *standardFeed);
+
+public slots:
+    void appear();
+    void disappear();
+    void load();
+    
+private slots:
+    void layoutCategories(const QList<YTCategory> &categories);
+    void selectWorldwideRegion();
+    void selectLocalRegion();
+
+private:
+    void addVideoSourceWidget(VideoSource *videoSource);
+    QList<YTStandardFeed*> getMainFeeds();
+    YTStandardFeed* buildStardardFeed(QString feedId, QString label);
+    QGridLayout *layout;
+    
+};
+
+#endif // CATEGORIESVIEW_H
diff --git a/src/userview.cpp b/src/userview.cpp
new file mode 100644 (file)
index 0000000..af6257a
--- /dev/null
@@ -0,0 +1,5 @@
+#include "userview.h"
+
+UserView::UserView(QWidget *parent) : QWidget(parent) {
+    layout = new QGridLayout(this);
+}
diff --git a/src/userview.h b/src/userview.h
new file mode 100644 (file)
index 0000000..bb03acd
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef USERVIEW_H
+#define USERVIEW_H
+
+#include <QtGui>
+#include "view.h"
+
+class VideoSource;
+
+class UserView : public QWidget, public View {
+
+    Q_OBJECT
+
+public:
+    UserView(QWidget *parent = 0);
+
+signals:
+    void activated(VideoSource *standardFeed);
+    
+private:
+    QGridLayout *layout;
+    
+};
+
+#endif // USERVIEW_H
index 4b8e247280ee1057654407c657b138584114e7bd..02647628c2d15f7e11bae3a83bbc8b62cdbbf6a7 100644 (file)
@@ -23,7 +23,8 @@ Video* Video::clone() {
     cloneVideo->m_webpage = m_webpage;
     cloneVideo->m_streamUrl = m_streamUrl;
     cloneVideo->m_thumbnail = m_thumbnail;
-    cloneVideo->m_thumbnailUrls = m_thumbnailUrls;
+    cloneVideo->m_thumbnailUrl = m_thumbnailUrl;
+    cloneVideo->m_mediumThumbnailUrl = m_mediumThumbnailUrl;
     cloneVideo->m_duration = m_duration;
     cloneVideo->m_published = m_published;
     cloneVideo->m_viewCount = m_viewCount;
@@ -33,19 +34,20 @@ Video* Video::clone() {
     return cloneVideo;
 }
 
-void Video::preloadThumbnail() {
-    if (m_thumbnailUrls.isEmpty()) return;
-    QObject *reply = The::http()->get(m_thumbnailUrls.first());
+void Video::loadThumbnail() {
+    QObject *reply = The::http()->get(m_thumbnailUrl);
     connect(reply, SIGNAL(data(QByteArray)), SLOT(setThumbnail(QByteArray)));
 }
 
 void Video::setThumbnail(QByteArray bytes) {
-    m_thumbnail = QImage::fromData(bytes);
+    m_thumbnail.loadFromData(bytes);
     emit gotThumbnail();
 }
 
-const QImage Video::thumbnail() const {
-    return m_thumbnail;
+void Video::loadMediumThumbnail() {
+    if (m_mediumThumbnailUrl.isEmpty()) return;
+    QObject *reply = The::http()->get(m_mediumThumbnailUrl);
+    connect(reply, SIGNAL(data(QByteArray)), SIGNAL(gotMediumThumbnail(QByteArray)));
 }
 
 void Video::loadStreamUrl() {
@@ -60,9 +62,10 @@ void Video::loadStreamUrl() {
     // Get Video ID
     // youtube-dl line 428
     // QRegExp re("^((?:http://)?(?:\\w+\\.)?youtube\\.com/(?:(?:v/)|(?:(?:watch(?:\\.php)?)?\\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$");
-    QRegExp re("^http://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+).*");
+    QRegExp re("^https?://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+).*");
     bool match = re.exactMatch(m_webpage.toString());
     if (!match || re.numCaptures() < 1) {
+        qDebug() << QString("Cannot get video id for %1").arg(m_webpage.toString());
         emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString()));
         loadingStreamUrl = false;
         return;
@@ -317,3 +320,9 @@ void Video::findVideoUrl(int definitionCode) {
     // see you in gotHeadHeaders()
 
 }
+
+
+QString Video::formattedDuration() const {
+    QString format = m_duration > 3600 ? "h:mm:ss" : "m:ss";
+    return QTime().addSecs(m_duration).toString(format);
+}
index d48a848c47ce93e8946d840066f05badb05a75da..e5eb07f74f744cfead11a73ed678a3283a7d07e6 100644 (file)
@@ -27,16 +27,19 @@ public:
     const QUrl webpage() const { return m_webpage; }
     void setWebpage( QUrl webpage ) { m_webpage = webpage; }
 
-    QList<QUrl> thumbnailUrls() const { return m_thumbnailUrls; }
-    void addThumbnailUrl(QUrl url) {
-        m_thumbnailUrls << url;
-    }
+    void loadThumbnail();
+    const QPixmap & thumbnail() const { return m_thumbnail; }
 
-    void preloadThumbnail();
-    const QImage thumbnail() const;
+    QString thumbnailUrl() { return m_thumbnailUrl; }
+    void setThumbnailUrl(QString url) { m_thumbnailUrl = url; }
+
+    void loadMediumThumbnail();
+    QString mediumThumbnailUrl() { return m_mediumThumbnailUrl; }
+    void setMediumThumbnailUrl(QString url) { m_mediumThumbnailUrl = url; }
 
     int duration() const { return m_duration; }
     void setDuration( int duration ) { m_duration = duration; }
+    QString formattedDuration() const;
 
     int viewCount() const { return m_viewCount; }
     void setViewCount( int viewCount ) { m_viewCount = viewCount; }
@@ -51,15 +54,14 @@ public:
 
     QString id() { return videoId; }
 
-public slots:
-    void setThumbnail(QByteArray bytes);
-
 signals:
     void gotThumbnail();
+    void gotMediumThumbnail(QByteArray bytes);
     void gotStreamUrl(QUrl streamUrl);
     void errorStreamUrl(QString message);
 
 private slots:
+    void setThumbnail(QByteArray bytes);
     void gotVideoInfo(QByteArray);
     void errorVideoInfo(QNetworkReply*);
     void scrapeWebPage(QByteArray);
@@ -76,16 +78,14 @@ private:
     QString m_authorUri;
     QUrl m_webpage;
     QUrl m_streamUrl;
-    QImage m_thumbnail;
-    QList<QUrl> m_thumbnailUrls;
+    QPixmap m_thumbnail;
+    QString m_thumbnailUrl;
+    QString m_mediumThumbnailUrl;
     int m_duration;
     QDateTime m_published;
     int m_viewCount;
 
-    // The YouTube video id
-    // This is needed by the gotVideoInfo callback
     QString videoId;
-
     QString videoToken;
     int definitionCode;
 
index 13fecd828164055aa3fa1b6010b9fc8ecc3d5830..695070f7141c2eb195ee42d5d0e9a07ce9b527a8 100644 (file)
@@ -31,9 +31,7 @@ VideoAreaWidget::VideoAreaWidget(QWidget *parent) : QWidget(parent) {
     snapshotPreview = new QLabel(this);
     stackedLayout->addWidget(snapshotPreview);
     
-    setLayout(vLayout);
-    setAcceptDrops(true);
-    
+    setAcceptDrops(true);    
     setMouseTracking(true);
 }
 
index 8f9db2fe424ded83a0a91f0fdf0cbcebb07815b5..4d4fd658463c2903289f461aa2307812f3e4ae67 100644 (file)
@@ -4,21 +4,21 @@
 #include <QWidget>
 #include "video.h"
 #include "loadingwidget.h"
-#include "listmodel.h"
+#include "playlistmodel.h"
 
 class VideoAreaWidget : public QWidget {
 
     Q_OBJECT
 
 public:
-    VideoAreaWidget(QWidget *parent);
+    VideoAreaWidget(QWidget *parent = 0);
     void setVideoWidget(QWidget *videoWidget);
     void setLoadingWidget(LoadingWidget *loadingWidget);
     void showLoading(Video* video);
     void showVideo();
     void showError(QString message);
     void clear();
-    void setListModel(ListModel *listModel) {
+    void setListModel(PlaylistModel *listModel) {
         this->listModel = listModel;
     }
     void showSnapshotPreview(QPixmap pixmap);
@@ -40,7 +40,7 @@ private:
     QStackedLayout *stackedLayout;
     QWidget *videoWidget;
     LoadingWidget *loadingWidget;
-    ListModel *listModel;
+    PlaylistModel *listModel;
     QLabel *messageLabel;
     QLabel *snapshotPreview;
 
diff --git a/src/videosource.cpp b/src/videosource.cpp
new file mode 100644 (file)
index 0000000..d2da7fb
--- /dev/null
@@ -0,0 +1,6 @@
+#include "videosource.h"
+
+void VideoSource::setParam(QString name, QVariant value) {
+    bool success = setProperty(name.toUtf8(), value);
+    if (!success) qWarning() << "Failed to set property" << name << value.toString();
+}
diff --git a/src/videosource.h b/src/videosource.h
new file mode 100644 (file)
index 0000000..25fcfbf
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef VIDEOSOURCE_H
+#define VIDEOSOURCE_H
+
+#include <QtCore>
+
+class Video;
+
+class VideoSource : public QObject {
+
+    Q_OBJECT
+
+public:
+    VideoSource(QObject *parent = 0) : QObject(parent) { }
+    virtual void loadVideos(int max, int skip) = 0;
+    virtual void abort() = 0;
+    virtual const QStringList & getSuggestions() = 0;
+    virtual QString getName() = 0;
+
+public slots:
+    void setParam(QString name, QVariant value);
+
+signals:
+    void gotVideo(Video *video);
+    void finished(int total);
+    void error(QString message);
+    void nameChanged(QString name);
+
+};
+
+#endif // VIDEOSOURCE_H
diff --git a/src/videosourcewidget.cpp b/src/videosourcewidget.cpp
new file mode 100644 (file)
index 0000000..d789a01
--- /dev/null
@@ -0,0 +1,171 @@
+#include "videosourcewidget.h"
+#include "videosource.h"
+#include "video.h"
+#include "fontutils.h"
+
+VideoSourceWidget::VideoSourceWidget(VideoSource *videoSource, QWidget *parent)
+    : QWidget(parent),
+      videoSource(videoSource),
+      hovered(false),
+      pressed(false) {
+
+    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    setCursor(Qt::PointingHandCursor);
+    setFocusPolicy(Qt::StrongFocus);
+
+    connect(videoSource, SIGNAL(gotVideo(Video*)),
+            SLOT(previewVideo(Video*)), Qt::UniqueConnection);
+    videoSource->loadVideos(1, 1);
+}
+
+void VideoSourceWidget::activate() {
+    emit activated(videoSource);
+}
+
+void VideoSourceWidget::previewVideo(Video *video) {
+    videoSource->disconnect();
+    this->video = video;
+    connect(video, SIGNAL(gotMediumThumbnail(QByteArray)),
+            SLOT(setPixmapData(QByteArray)), Qt::UniqueConnection);
+    video->loadMediumThumbnail();
+}
+
+void VideoSourceWidget::setPixmapData(QByteArray bytes) {
+    video->deleteLater();
+    video = 0;
+    pixmap.loadFromData(bytes);
+    update();
+}
+
+QPixmap VideoSourceWidget::playPixmap() {
+    const int s = height() / 2;
+    const int padding = s / 8;
+    QPixmap playIcon = QPixmap(s, s);
+    playIcon.fill(Qt::transparent);
+    QPainter painter(&playIcon);
+    QPolygon polygon;
+    polygon << QPoint(padding, padding)
+            << QPoint(s - padding, s / 2)
+            << QPoint(padding, s - padding);
+    painter.setRenderHints(QPainter::Antialiasing, true);
+
+    // QColor color = pressed ? Qt::black : Qt::white;
+    QColor color = Qt::white;
+    painter.setBrush(color);
+    QPen pen;
+    pen.setColor(color);
+    pen.setWidth(10);
+    pen.setJoinStyle(Qt::RoundJoin);
+    pen.setCapStyle(Qt::RoundCap);
+    painter.setPen(pen);
+    painter.drawPolygon(polygon);
+    return playIcon;
+}
+
+void VideoSourceWidget::paintEvent(QPaintEvent *) {
+    if (pixmap.isNull()) return;
+
+    QPainter p(this);
+
+    const int w = width();
+    const int h = height();
+
+    int xOffset = 0;
+    int xOrigin = 0;
+    int wDiff = pixmap.width() - w;
+    if (wDiff > 0) xOffset = wDiff / 2;
+    else xOrigin = -wDiff / 2;
+    int yOffset = 0;
+    int yOrigin = 0;
+    int hDiff = pixmap.height() - h;
+    if (hDiff > 0) yOffset = hDiff / 4;
+    else yOrigin = -hDiff / 2;
+    p.drawPixmap(xOrigin, yOrigin, pixmap, xOffset, yOffset, w, h);
+
+    if (hovered) {
+        QPixmap play = playPixmap();
+        p.save();
+        p.setOpacity(.5);
+        p.drawPixmap(
+                    (w - play.width()) / 2,
+                    (h * 2/3 - play.height()) / 2,
+                    play
+                    );
+        p.restore();
+    }
+
+    QRect nameBox = rect();
+    nameBox.adjust(0, 0, 0, -h*2/3);
+    nameBox.translate(0, h - nameBox.height());
+    p.save();
+    p.setPen(Qt::NoPen);
+    p.setBrush(QColor(0, 0, 0, 128));
+    p.drawRect(nameBox);
+    p.restore();
+
+    QString name = videoSource->getName();
+    bool tooBig = false;
+    p.save();
+    p.setFont(FontUtils::medium());
+    QRect textBox = p.boundingRect(nameBox, Qt::AlignCenter | Qt::TextWordWrap, name);
+    if (textBox.height() > nameBox.height()) {
+        p.setFont(font());
+        textBox = p.boundingRect(nameBox, Qt::AlignCenter | Qt::TextWordWrap, name);
+        if (textBox.height() > nameBox.height()) {
+            p.setClipRect(nameBox);
+            tooBig = true;
+        }
+    }
+    p.setPen(Qt::white);
+    if (tooBig)
+        p.drawText(nameBox, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, name);
+    else
+        p.drawText(textBox, Qt::AlignCenter | Qt::TextWordWrap, name);
+    p.restore();
+
+    if (hasFocus()) {
+        p.save();
+        QPen pen;
+        pen.setBrush(palette().highlight());
+        pen.setWidth(2);
+        p.setPen(pen);
+        p.drawRect(rect());
+        p.restore();
+    }
+}
+
+void VideoSourceWidget::mouseMoveEvent (QMouseEvent *event) {
+    QWidget::mouseMoveEvent(event);
+    hovered = rect().contains(event->pos());
+}
+
+void VideoSourceWidget::mousePressEvent(QMouseEvent *event) {
+    QWidget::mousePressEvent(event);
+    if (event->button() != Qt::LeftButton) return;
+    pressed = true;
+    update();
+}
+
+void VideoSourceWidget::mouseReleaseEvent(QMouseEvent *event) {
+    QWidget::mouseReleaseEvent(event);
+    if (event->button() != Qt::LeftButton) return;
+    pressed = false;
+    if (hovered) emit activated(videoSource);
+}
+
+void VideoSourceWidget::leaveEvent(QEvent *event) {
+    QWidget::leaveEvent(event);
+    hovered = false;
+    update();
+}
+
+void VideoSourceWidget::enterEvent(QEvent *event) {
+    QWidget::enterEvent(event);
+    hovered = true;
+    update();
+}
+
+void VideoSourceWidget::keyReleaseEvent(QKeyEvent *event) {
+    if (event->key() == Qt::Key_Return)
+        emit activated(videoSource);
+}
diff --git a/src/videosourcewidget.h b/src/videosourcewidget.h
new file mode 100644 (file)
index 0000000..2316fcc
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef VIDEOSOURCEWIDGET_H
+#define VIDEOSOURCEWIDGET_H
+
+#include <QtGui>
+
+class Video;
+class VideoSource;
+
+class VideoSourceWidget : public QWidget {
+
+    Q_OBJECT
+
+public:
+    VideoSourceWidget(VideoSource *videoSource, QWidget *parent = 0);
+
+signals:
+    void activated(VideoSource *videoSource);
+
+protected:
+    void paintEvent(QPaintEvent *);
+    void mouseMoveEvent(QMouseEvent *event);
+    void mousePressEvent(QMouseEvent *event);
+    void mouseReleaseEvent(QMouseEvent *event);
+    void enterEvent(QEvent *event);
+    void leaveEvent(QEvent *event);
+    void keyReleaseEvent(QKeyEvent *event);
+
+private slots:
+    void activate();
+    void previewVideo(Video*);
+    void setPixmapData(QByteArray bytes);
+
+private:
+    QPixmap playPixmap();
+    VideoSource *videoSource;
+    QPixmap pixmap;
+    Video *video;
+
+    bool hovered;
+    bool pressed;
+};
+
+#endif // VIDEOSOURCEWIDGET_H
diff --git a/src/ytcategories.cpp b/src/ytcategories.cpp
new file mode 100644 (file)
index 0000000..569fcd3
--- /dev/null
@@ -0,0 +1,53 @@
+#include "ytcategories.h"
+#include "networkaccess.h"
+#include <QtXml>
+
+namespace The {
+NetworkAccess* http();
+}
+
+YTCategories::YTCategories(QObject *parent) : QObject(parent) { }
+
+void YTCategories::loadCategories() {
+    QString url = "http://gdata.youtube.com/schemas/2007/categories.cat?hl=";
+#if QT_VERSION >= 0x040800
+    url += QLocale::system().uiLanguages().first();
+#else
+    url += QLocale::system().name().replace('_', '-');
+#endif
+    qDebug() << url;
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(parseCategories(QByteArray)));
+    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
+}
+
+void YTCategories::parseCategories(QByteArray bytes) {
+    QList<YTCategory> categories;
+
+    QXmlStreamReader xml(bytes);
+    while (!xml.atEnd()) {
+        xml.readNext();
+        if (xml.isStartElement() && xml.name() == "category") {
+            QString term = xml.attributes().value("term").toString();
+            QString label = xml.attributes().value("label").toString();
+            while(xml.readNextStartElement())
+                if (xml.name() == "assignable") {
+                    YTCategory category;
+                    category.term = term;
+                    category.label = label;
+                    categories << category;
+                } else xml.skipCurrentElement();
+        }
+    }
+
+    if (xml.hasError()) {
+        emit error(xml.errorString());
+        return;
+    }
+
+    emit categoriesLoaded(categories);
+}
+
+void YTCategories::requestError(QNetworkReply *reply) {
+    emit error(reply->errorString());
+}
diff --git a/src/ytcategories.h b/src/ytcategories.h
new file mode 100644 (file)
index 0000000..1dec6d4
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef YTCATEGORIES_H
+#define YTCATEGORIES_H
+
+#include <QtNetwork>
+
+struct YTCategory {
+    QString term;
+    QString label;
+};
+
+class YTCategories : public QObject {
+
+    Q_OBJECT
+
+public:
+    YTCategories(QObject *parent = 0);
+    void loadCategories();
+    
+signals:
+    void categoriesLoaded(const QList<YTCategory> &);
+    void error(QString message);
+
+private slots:
+    void parseCategories(QByteArray bytes);
+    void requestError(QNetworkReply *reply);
+
+};
+
+#endif // YTCATEGORIES_H
index 78a9675bc2dd4d06df35b6eae390bc0487ffe36f..342e9ebf672a58b77318888aaf227ffdc47248e8 100644 (file)
@@ -1,42 +1,21 @@
-#include "youtubestreamreader.h"
-#include <QtGui>
-
-
-YouTubeStreamReader::YouTubeStreamReader() {
-
-}
-
-bool YouTubeStreamReader::read(QByteArray data) {
-    addData(data);
+#include "ytfeedreader.h"
+#include "video.h"
 
+YTFeedReader::YTFeedReader(const QByteArray &bytes) : QXmlStreamReader(bytes) {
     while (!atEnd()) {
         readNext();
-        if (isStartElement()) {
-            if (name() == "feed") {
-                while (!atEnd()) {
-                    readNext();
-                    if (isStartElement() && name() == "entry") {
-                        readEntry();
-                    } else if (name() == "link"
-                        && attributes().value("rel").toString()
-                           == "http://schemas.google.com/g/2006#spellcorrection") {
-                        suggestions << attributes().value("title").toString();
-                    }
-                }
-            }
+        if (isStartElement() && name() == "entry") {
+            readEntry();
+        } else if (name() == "link"
+                   && attributes().value("rel").toString()
+                   == "http://schemas.google.com/g/2006#spellcorrection") {
+            suggestions << attributes().value("title").toString();
         }
     }
-
-    return !error();
-}
-
-void YouTubeStreamReader::readMediaGroup() {
-
 }
 
-void YouTubeStreamReader::readEntry() {
+void YTFeedReader::readEntry() {
     Video* video = new Video();
-    // qDebug(" *** ENTRY ***");
 
     while (!atEnd()) {
         readNext();
@@ -52,9 +31,9 @@ void YouTubeStreamReader::readEntry() {
         if (isStartElement()) {
 
             if (name() == "link"
-                && attributes().value("rel").toString() == "alternate"
-                && attributes().value("type").toString() == "text/html"
-                ) {
+                    && attributes().value("rel").toString() == "alternate"
+                    && attributes().value("type").toString() == "text/html"
+                    ) {
                 QString webpage = attributes().value("href").toString();
                 webpage.remove("&feature=youtube_gdata");
                 video->setWebpage(QUrl(webpage));
@@ -85,8 +64,13 @@ void YouTubeStreamReader::readEntry() {
                     if (isStartElement()) {
                         if (name() == "thumbnail") {
                             // qDebug() << "Thumb: " << attributes().value("url").toString();
-                            // video->thumbnailUrls() << QUrl(attributes().value("url").toString());
-                            video->addThumbnailUrl(QUrl(attributes().value("url").toString()));
+                            QStringRef name = attributes().value("yt:name");
+                            if (name == "default")
+                                video->setThumbnailUrl(
+                                            attributes().value("url").toString());
+                            else if (name == "hqdefault")
+                                video->setMediumThumbnailUrl(
+                                            attributes().value("url").toString());
                         }
                         else if (name() == "title") {
                             QString title = readElementText();
@@ -113,10 +97,10 @@ void YouTubeStreamReader::readEntry() {
 
 }
 
-QList<Video*> YouTubeStreamReader::getVideos() {
+const QList<Video *> &YTFeedReader::getVideos() {
     return videos;
 }
 
-const QStringList & YouTubeStreamReader::getSuggestions() const {
+const QStringList & YTFeedReader::getSuggestions() const {
     return suggestions;
 }
index 8c8c617dc988ea2051e086d6115b1fd5c666331c..3ee6142d616260701974dd52dcc70dabc1ab34b4 100644 (file)
@@ -1,23 +1,21 @@
-#ifndef YOUTUBESTREAMREADER_H
-#define YOUTUBESTREAMREADER_H
+#ifndef YTFEEDREADER_H
+#define YTFEEDREADER_H
 
-#include <QXmlStreamReader>
-#include <QBuffer>
-#include "video.h"
+#include <QtXml>
+
+class Video;
+
+class YTFeedReader : public QXmlStreamReader {
 
-class YouTubeStreamReader : public QXmlStreamReader
-{
 public:
-    YouTubeStreamReader();
-    bool read(QByteArray data);
-    QList<Video*> getVideos();
+    YTFeedReader(const QByteArray &bytes);
+    const QList<Video*> & getVideos();
     const QStringList & getSuggestions() const;
 
 private:
-    void readMediaGroup();
     void readEntry();
     QList<Video*> videos;
     QStringList suggestions;
 };
 
-#endif // YOUTUBESTREAMREADER_H
+#endif // YTFEEDREADER_H
diff --git a/src/ytregions.cpp b/src/ytregions.cpp
new file mode 100644 (file)
index 0000000..89390a9
--- /dev/null
@@ -0,0 +1,161 @@
+#include "ytregions.h"
+
+YTRegions::YTRegions() : QObject() { }
+
+const QList<YTRegion> & YTRegions::list() {
+    static QList<YTRegion> list;
+    if (list.isEmpty()) {
+        list << r(tr("Algeria"), "DZ")
+             << r(tr("Argentina"), "AR")
+             << r(tr("Australia"), "AU")
+             << r(tr("Belgium"), "BE")
+             << r(tr("Brazil"), "BR")
+             << r(tr("Canada"), "CA")
+             << r(tr("Chile"), "CL")
+             << r(tr("Colombia"), "CO")
+             << r(tr("Czech Republic"), "CZ")
+             << r(tr("Egypt"), "EG")
+             << r(tr("France"), "FR")
+             << r(tr("Germany"), "DE")
+             << r(tr("Ghana"), "GH")
+             << r(tr("Greece"), "GR")
+             << r(tr("Hong Kong"), "HK")
+             << r(tr("Hungary"), "HU")
+             << r(tr("India"), "IN")
+             << r(tr("Indonesia"), "ID")
+             << r(tr("Ireland"), "IE")
+             << r(tr("Israel"), "IL")
+             << r(tr("Italy"), "IT")
+             << r(tr("Japan"), "JP")
+             << r(tr("Jordan"), "JO")
+             << r(tr("Kenya"), "KE")
+             << r(tr("Malaysia"), "MY")
+             << r(tr("Mexico"), "MX")
+             << r(tr("Morocco"), "MA")
+             << r(tr("Netherlands"), "NL")
+             << r(tr("New Zealand"), "NZ")
+             << r(tr("Nigeria"), "NG")
+             << r(tr("Peru"), "PE")
+             << r(tr("Philippines"), "PH")
+             << r(tr("Poland"), "PL")
+             << r(tr("Russia"), "RU")
+             << r(tr("Saudi Arabia"), "SA")
+             << r(tr("Singapore"), "SG")
+             << r(tr("South Africa"), "ZA")
+             << r(tr("South Korea"), "KR")
+             << r(tr("Spain"), "ES")
+             << r(tr("Sweden"), "SE")
+             << r(tr("Taiwan"), "TW")
+             << r(tr("Tunisia"), "TN")
+             << r(tr("Turkey"), "TR")
+             << r(tr("Uganda"), "UG")
+             << r(tr("United Arab Emirates"), "AE")
+             << r(tr("United Kingdom"), "GB")
+             << r(tr("Yemen"), "YE");
+/*
+        list << r(QLocale::Algeria, "DZ")
+             << r(QLocale::Argentina, "AR")
+             << r(QLocale::Australia, "AU")
+             << r(QLocale::Belgium, "BE")
+             << r(QLocale::Brazil, "BR")
+             << r(QLocale::Canada, "CA")
+             << r(QLocale::Chile, "CL")
+             << r(QLocale::Colombia, "CO")
+             << r(QLocale::CzechRepublic, "CZ")
+             << r(QLocale::Egypt, "EG")
+             << r(QLocale::France, "FR")
+             << r(QLocale::Germany, "DE")
+             << r(QLocale::Ghana, "GH")
+             << r(QLocale::Greece, "GR")
+             << r(QLocale::HongKong, "HK")
+             << r(QLocale::Hungary, "HU")
+             << r(QLocale::India, "IN")
+             << r(QLocale::Indonesia, "ID")
+             << r(QLocale::Ireland, "IE")
+             << r(QLocale::Israel, "IL")
+             << r(QLocale::Italy, "IT")
+             << r(QLocale::Japan, "JP")
+             << r(QLocale::Jordan, "JO")
+             << r(QLocale::Kenya, "KE")
+             << r(QLocale::Malaysia, "MY")
+             << r(QLocale::Mexico, "MX")
+             << r(QLocale::Morocco, "MA")
+             << r(QLocale::Netherlands, "NL")
+             << r(QLocale::NewZealand, "NZ")
+             << r(QLocale::Nigeria, "NG")
+             << r(QLocale::Peru, "PE")
+             << r(QLocale::Philippines, "PH")
+             << r(QLocale::Poland, "PL")
+             << r(QLocale::RussianFederation, "RU")
+             << r(QLocale::SaudiArabia, "SA")
+             << r(QLocale::Singapore, "SG")
+             << r(QLocale::SouthAfrica, "ZA")
+             << r(QLocale::RepublicOfKorea, "KR")
+             << r(QLocale::Spain, "ES")
+             << r(QLocale::Sweden, "SE")
+             << r(QLocale::Taiwan, "TW")
+             << r(QLocale::Tunisia, "TN")
+             << r(QLocale::Turkey, "TR")
+             << r(QLocale::Uganda, "UG")
+             << r(QLocale::UnitedArabEmirates, "AE")
+             << r(QLocale::UnitedKingdom, "GB")
+             << r(QLocale::Yemen, "YE");
+             */
+        qSort(list);
+    }
+    return list;
+}
+
+YTRegion YTRegions::r(QString name, QString id) {
+    YTRegion r = {id, name};
+    return r;
+}
+
+const YTRegion & YTRegions::localRegion() {
+    static YTRegion region;
+    if (region.name.isEmpty()) {
+        QString country = QLocale::system().name().right(2);
+        foreach (YTRegion r, list())
+            if (r.id == country) {
+                region = r;
+                break;
+            } // else qDebug() << r.id << country;
+    }
+    return region;
+}
+
+const YTRegion & YTRegions::worldwideRegion() {
+    static YTRegion region = {"", tr("Worldwide")};
+    return region;
+}
+
+void YTRegions::setRegion(QString regionId) {
+    QSettings settings;
+    settings.setValue("regionId", regionId);
+}
+
+QString YTRegions::currentRegionId() {
+    QSettings settings;
+    return settings.value("regionId").toString();
+}
+
+YTRegion YTRegions::currentRegion() {
+    return regionById(currentRegionId());
+}
+
+YTRegion YTRegions::regionById(QString id) {
+    if (id.isEmpty()) return worldwideRegion();
+    YTRegion region;
+    foreach (YTRegion r, list())
+        if (r.id == id) {
+            region = r;
+            break;
+        }
+    if (region.name.isEmpty()) return worldwideRegion();
+    return region;
+}
+
+QIcon YTRegions::iconForRegionId(QString regionId) {
+    if (regionId.isEmpty()) return QIcon(":flags/worldwide.png");
+    return QIcon(":flags/" + regionId.toLower() + ".png");
+}
diff --git a/src/ytregions.h b/src/ytregions.h
new file mode 100644 (file)
index 0000000..029c0b9
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef YTREGIONS_H
+#define YTREGIONS_H
+
+#include <QtGui>
+
+struct YTRegion {
+    QString id;
+    QString name;
+    bool operator<(const YTRegion &other) const {
+        return name < other.name;
+    }
+};
+
+class YTRegions : public QObject {
+
+public:
+    static const QList<YTRegion> & list();
+    static const YTRegion & localRegion();
+    static const YTRegion & worldwideRegion();
+    static void setRegion(QString regionId);
+    static QString currentRegionId();
+    static YTRegion currentRegion();
+    static QIcon iconForRegionId(QString regionId);
+
+private:
+    static YTRegion r(QString name, QString id);
+    static YTRegion regionById(QString id);
+    YTRegions();
+
+};
+
+#endif // YTREGIONS_H
diff --git a/src/ytsearch.cpp b/src/ytsearch.cpp
new file mode 100644 (file)
index 0000000..a5d03df
--- /dev/null
@@ -0,0 +1,127 @@
+#include "ytsearch.h"
+#include "ytfeedreader.h"
+#include "constants.h"
+#include "networkaccess.h"
+#include "searchparams.h"
+#include "video.h"
+
+namespace The {
+    NetworkAccess* http();
+}
+
+YTSearch::YTSearch(SearchParams *searchParams, QObject *parent) :
+    VideoSource(parent),
+    searchParams(searchParams) {
+    searchParams->setParent(this);
+}
+
+void YTSearch::loadVideos(int max, int skip) {
+    this->aborted = false;
+
+    QUrl url("https://gdata.youtube.com/feeds/api/videos/");
+    url.addQueryItem("v", "2");
+
+    url.addQueryItem("max-results", QString::number(max));
+    url.addQueryItem("start-index", QString::number(skip));
+
+    if (!searchParams->keywords().isEmpty()) {
+        if (searchParams->keywords().startsWith("http://") ||
+                searchParams->keywords().startsWith("https://")) {
+            url.addQueryItem("q", YTSearch::videoIdFromUrl(searchParams->keywords()));
+        } else url.addQueryItem("q", searchParams->keywords());
+    }
+
+    if (!searchParams->author().isEmpty())
+        url.addQueryItem("author", searchParams->author());
+
+    switch (searchParams->sortBy()) {
+    case SearchParams::SortByNewest:
+        url.addQueryItem("orderby", "published");
+        break;
+    case SearchParams::SortByViewCount:
+        url.addQueryItem("orderby", "viewCount");
+        break;
+    case SearchParams::SortByRating:
+        url.addQueryItem("orderby", "rating");
+        break;
+    }
+
+    switch (searchParams->duration()) {
+    case SearchParams::DurationShort:
+        url.addQueryItem("duration", "short");
+        break;
+    case SearchParams::DurationMedium:
+        url.addQueryItem("duration", "medium");
+        break;
+    case SearchParams::DurationLong:
+        url.addQueryItem("duration", "long");
+        break;
+    }
+
+    switch (searchParams->time()) {
+    case SearchParams::TimeToday:
+        url.addQueryItem("time", "today");
+        break;
+    case SearchParams::TimeWeek:
+        url.addQueryItem("time", "this_week");
+        break;
+    case SearchParams::TimeMonth:
+        url.addQueryItem("time", "this_month");
+        break;
+    }
+
+    switch (searchParams->quality()) {
+    case SearchParams::QualityHD:
+        url.addQueryItem("hd", "true");
+        break;
+    }
+
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
+    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
+}
+
+void YTSearch::abort() {
+    aborted = true;
+}
+
+const QStringList & YTSearch::getSuggestions() {
+    return suggestions;
+}
+
+QString YTSearch::getName() {
+    if (!name.isEmpty()) return name;
+    if (!searchParams->keywords().isEmpty()) return searchParams->keywords();
+    return QString();
+}
+
+void YTSearch::parseResults(QByteArray data) {
+    if (aborted) return;
+
+    YTFeedReader reader(data);
+    QList<Video*> videos = reader.getVideos();
+    suggestions = reader.getSuggestions();
+
+    if (!searchParams->author().isEmpty()) {
+        if (videos.isEmpty()) name = searchParams->author();
+        else name = videos.first()->author();
+        emit nameChanged(name);
+    }
+
+    foreach (Video *video, videos)
+        emit gotVideo(video);
+
+    emit finished(videos.size());
+}
+
+void YTSearch::requestError(QNetworkReply *reply) {
+    emit error(reply->errorString());
+}
+
+QString YTSearch::videoIdFromUrl(QString url) {
+    QRegExp re = QRegExp("^.*[\\?&]v=([^&#]+).*$");
+    if (re.exactMatch(url)) return re.cap(1);
+    re = QRegExp("^.*://.*/([^&#\\?]+).*$");
+    if (re.exactMatch(url)) return re.cap(1);
+    return QString();
+}
diff --git a/src/ytsearch.h b/src/ytsearch.h
new file mode 100644 (file)
index 0000000..983a27b
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef YTSEARCH_H
+#define YTSEARCH_H
+
+#include <QtNetwork>
+#include "videosource.h"
+
+class SearchParams;
+class Video;
+
+class YTSearch : public VideoSource {
+
+    Q_OBJECT
+
+public:
+    YTSearch(SearchParams *params, QObject *parent = 0);
+    void loadVideos(int max, int skip);
+    virtual void abort();
+    virtual const QStringList & getSuggestions();
+    static QString videoIdFromUrl(QString url);
+    QString getName();
+    SearchParams* getSearchParams() const { return searchParams; }
+
+    bool operator==(const YTSearch &other) const {
+        return searchParams == other.getSearchParams();
+    }
+
+private slots:
+    void parseResults(QByteArray data);
+    void requestError(QNetworkReply *reply);
+
+private:
+    SearchParams *searchParams;
+    bool aborted;
+    QStringList suggestions;
+    QString name;
+};
+
+#endif // YTSEARCH_H
diff --git a/src/ytsinglevideosource.cpp b/src/ytsinglevideosource.cpp
new file mode 100644 (file)
index 0000000..c6130d4
--- /dev/null
@@ -0,0 +1,66 @@
+#include "ytsinglevideosource.h"
+#include <QtXml>
+#include "networkaccess.h"
+#include "video.h"
+#include "ytfeedreader.h"
+
+namespace The {
+NetworkAccess* http();
+}
+
+YTSingleVideoSource::YTSingleVideoSource(QObject *parent) : VideoSource(parent) {
+    skip = 0;
+    max = 0;
+}
+
+void YTSingleVideoSource::loadVideos(int max, int skip) {
+    aborted = false;
+    this->skip = skip;
+    this->max = max;
+
+    QString s;
+    if (skip == 1) s = "https://gdata.youtube.com/feeds/api/videos/" + videoId;
+    else s = QString("https://gdata.youtube.com/feeds/api/videos/%1/related").arg(videoId);
+    QUrl url(s);
+    url.addQueryItem("v", "2");
+
+    if (skip != 1) {
+        url.addQueryItem("max-results", QString::number(max));
+        url.addQueryItem("start-index", QString::number(skip-1));
+    }
+
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(parse(QByteArray)));
+    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
+}
+
+void YTSingleVideoSource::abort() {
+    aborted = true;
+}
+
+const QStringList & YTSingleVideoSource::getSuggestions() {
+    QStringList *l = new QStringList();
+    return *l;
+}
+
+QString YTSingleVideoSource::getName() {
+    return QString();
+}
+
+void YTSingleVideoSource::parse(QByteArray data) {
+    if (aborted) return;
+
+    YTFeedReader reader(data);
+    QList<Video*> videos = reader.getVideos();
+
+    foreach (Video *video, videos)
+        emit gotVideo(video);
+
+    if (skip == 1) loadVideos(max - 1, 2);
+    else if (skip == 2) emit finished(videos.size() + 1);
+    else emit finished(videos.size());
+}
+
+void YTSingleVideoSource::requestError(QNetworkReply *reply) {
+    emit error(reply->errorString());
+}
diff --git a/src/ytsinglevideosource.h b/src/ytsinglevideosource.h
new file mode 100644 (file)
index 0000000..f60d812
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef YTSINGLEVIDEOSOURCE_H
+#define YTSINGLEVIDEOSOURCE_H
+
+#include <QtNetwork>
+#include "videosource.h"
+
+class YTSingleVideoSource : public VideoSource {
+
+    Q_OBJECT
+
+public:
+    YTSingleVideoSource(QObject *parent = 0);
+    void loadVideos(int max, int skip);
+    void abort();
+    const QStringList & getSuggestions();
+    QString getName();
+
+    void setVideoId(QString videoId) { this->videoId = videoId; }
+
+private slots:
+    void parse(QByteArray data);
+    void requestError(QNetworkReply *reply);
+
+private:
+    QString videoId;
+    bool aborted;
+    int skip;
+    int max;
+};
+
+#endif // YTSINGLEVIDEOSOURCE_H
diff --git a/src/ytstandardfeed.cpp b/src/ytstandardfeed.cpp
new file mode 100644 (file)
index 0000000..8eb35a3
--- /dev/null
@@ -0,0 +1,60 @@
+#include "ytstandardfeed.h"
+#include <QtXml>
+#include "networkaccess.h"
+#include "video.h"
+#include "ytfeedreader.h"
+
+namespace The {
+NetworkAccess* http();
+}
+
+YTStandardFeed::YTStandardFeed(QObject *parent)
+    : VideoSource(parent),
+      aborted(false) { }
+
+void YTStandardFeed::loadVideos(int max, int skip) {
+    aborted = false;
+
+    QString s = "https://gdata.youtube.com/feeds/api/standardfeeds/";
+    if (!regionId.isEmpty()) s += regionId + "/";
+    s += feedId;
+    if (!category.isEmpty()) s += "_" + category;
+
+    QUrl url(s);
+    url.addQueryItem("v", "2");
+
+    if (feedId != "most_shared")
+        url.addQueryItem("time", "today");
+
+    url.addQueryItem("max-results", QString::number(max));
+    url.addQueryItem("start-index", QString::number(skip));
+
+    QObject *reply = The::http()->get(url);
+    connect(reply, SIGNAL(data(QByteArray)), SLOT(parse(QByteArray)));
+    connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
+}
+
+void YTStandardFeed::abort() {
+    aborted = true;
+}
+
+const QStringList & YTStandardFeed::getSuggestions() {
+    QStringList *l = new QStringList();
+    return *l;
+}
+
+void YTStandardFeed::parse(QByteArray data) {
+    if (aborted) return;
+
+    YTFeedReader reader(data);
+    QList<Video*> videos = reader.getVideos();
+
+    foreach (Video *video, videos)
+        emit gotVideo(video);
+
+    emit finished(videos.size());
+}
+
+void YTStandardFeed::requestError(QNetworkReply *reply) {
+    emit error(reply->errorString());
+}
diff --git a/src/ytstandardfeed.h b/src/ytstandardfeed.h
new file mode 100644 (file)
index 0000000..45cdfac
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef YTSTANDARDFEED_H
+#define YTSTANDARDFEED_H
+
+#include <QtNetwork>
+#include "videosource.h"
+
+class YTStandardFeed : public VideoSource {
+
+    Q_OBJECT
+
+public:
+    YTStandardFeed(QObject *parent = 0);
+
+    QString getFeedId() { return feedId; }
+    void setFeedId(QString feedId) { this->feedId = feedId; }
+
+    QString getRegionId() { return regionId; }
+    void setRegionId(QString regionId) { this->regionId = regionId; }
+
+    QString getCategory() { return category; }
+    void setCategory(QString category) { this->category = category; }
+
+    QString getLabel() { return label; }
+    void setLabel(QString label) { this->label = label; }
+
+    void loadVideos(int max, int skip);
+    void abort();
+    const QStringList & getSuggestions();
+    QString getName() { return label; }
+
+private slots:
+    void parse(QByteArray data);
+    void requestError(QNetworkReply *reply);
+
+private:
+    QString feedId;
+    QString regionId;
+    QString category;
+    QString label;
+    bool aborted;
+};
+
+#endif // YTSTANDARDFEED_H
index c1965ca724cd069c49c4b487666af087f9315b9b..6d1226e295957c978d552fc05d6b78dbe4dff62b 100644 (file)
@@ -1,4 +1,4 @@
-#include "youtubesuggest.h"
+#include "ytsuggester.h"
 #include <QtXml>
 #include "networkaccess.h"
 
@@ -8,12 +8,19 @@ namespace The {
     NetworkAccess* http();
 }
 
-YouTubeSuggest::YouTubeSuggest(QObject *parent) : Suggester() {
+YTSuggester::YTSuggester(QObject *parent) : Suggester() {
 
 }
 
-void YouTubeSuggest::suggest(QString query) {
+void YTSuggester::suggest(QString query) {
+    if (query.startsWith("http")) return;
+
+#if QT_VERSION >= 0x040800
+    QString locale = QLocale::system().uiLanguages().first();
+#else
     QString locale = QLocale::system().name().replace("_", "-");
+#endif
+
     // case for system locales such as "C"
     if (locale.length() < 2) {
         locale = "en-US";
@@ -25,7 +32,7 @@ void YouTubeSuggest::suggest(QString query) {
     connect(reply, SIGNAL(data(QByteArray)), SLOT(handleNetworkData(QByteArray)));
 }
 
-void YouTubeSuggest::handleNetworkData(QByteArray response) {
+void YTSuggester::handleNetworkData(QByteArray response) {
     QStringList choices;
 
     QXmlStreamReader xml(response);
index 2d2221e3656623a6432523be54bda55ebc723cf4..c42440b10ea11c0a0658fde747f6a17b7f07a940 100644 (file)
@@ -1,16 +1,15 @@
-#ifndef YOUTUBESUGGEST_H
-#define YOUTUBESUGGEST_H
+#ifndef YTSUGGESTER_H
+#define YTSUGGESTER_H
 
 #include <QtCore>
-
 #include "suggester.h"
 
-class YouTubeSuggest : public Suggester {
+class YTSuggester : public Suggester {
 
     Q_OBJECT
 
 public:
-    YouTubeSuggest(QObject *parent = 0);
+    YTSuggester(QObject *parent = 0);
     void suggest(QString query);
 
 signals:
@@ -21,4 +20,4 @@ private slots:
 
 };
 
-#endif // YOUTUBESUGGEST_H
+#endif // YTSUGGESTER_H
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..3f6c9e4
--- /dev/null
+++ b/style.css
@@ -0,0 +1,60 @@
+RegionsView QPushButton[regionId] {
+    margin: 5px;
+    padding: 10px;
+    text-align: left;
+    vertical-align: middle;
+    background-color: transparent;
+    border: 1px solid transparent;
+    border-radius: 10px;
+}
+
+RegionsView QPushButton[regionId=""] {
+    font-weight: bold;
+}
+
+RegionsView QPushButton[regionId]:hover {
+    border: 1px solid rgba(0,0,0,32);
+    background: rgba(0,0,0,16);
+}
+
+RegionsView QPushButton[regionId]:focus {
+    border: 1px solid palette(highlight);
+    background: rgba(0,0,0,16);
+}
+
+RegionsView QPushButton[regionId]:checked {
+    color: #fff;
+    border: 1px solid rgba(0,0,0,64);
+    background: qradialgradient(cx: 0.5, cy: 0,
+    fx: 0.5, fy: 0,
+    radius: 1.35, stop: 0 rgba(0,0,0,128), stop: 1 rgba(0,0,0,64));
+}
+
+SidebarHeader {
+    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+                         stop: 0 #262626, stop: 1 #3c3c3c);
+    border: 0;
+    padding: 0;
+    margin: 0;
+    spacing: 0;
+}
+
+SidebarHeader QToolButton {
+    border: 0;
+    padding: 0;
+    margin: 0;
+    spacing: 0;
+    height: 20;
+    width: 24;
+}
+
+SidebarHeader QPushButton {
+    background: transparent;
+    color: #fff;
+    text-align: center;
+}
+
+SidebarHeader QComboBox::drop-down {
+    width: 0;
+    border-style: none;
+}