1 from PyQt4
import QtGui
, QtCore
, QtSvg
2 from PyQt4
.QtGui
import QPalette
5 from traceback
import print_exc
9 from clSong
import Song
10 from clSettings
import settings
13 # constants used for fSongs
18 LIB_EXPANDED
=4 # values: 0, 1 or 2 (==song)
21 class DoUpdate(QtCore
.QEvent
):
23 QtCore
.QEvent
.__init
__(self
, QtCore
.QEvent
.User
)
24 class DoResize(QtCore
.QEvent
):
26 QtCore
.QEvent
.__init
__(self
, QtCore
.QEvent
.User
)
28 class SongList(QtGui
.QWidget
):
29 """The SongList widget is a list optimized for displaying an array of songs, with filtering option."""
30 # CONFIGURATION VARIABLES
32 fontSize
=12 #TODO:make this selectable
33 " height of line in pxl"
34 lineHeight
= fontSize
+ 4
37 vmargin
=(lineHeight
-fontSize
)/2-1
38 " width of the vscrollbar"
40 " minimum column width"
44 " colors for alternating rows"
45 colors
= [palette
.color(QPalette
.Base
), palette
.color(QPalette
.AlternateBase
)]
47 clrSel
= palette
.color(QPalette
.Highlight
)
49 clrBg
= palette
.color(QPalette
.Window
)
50 " indentation of hierarchy, in pixels"
51 indentation
=lineHeight
53 " what function to call when the list is double clicked"
56 mode
='playlist' # what mode is the songlist in? values: 'playlist', 'library'
57 " the headers: ( (header, width, visible)+ )"
59 songs
=None # original songs
60 numSongs
=None # number of songs
63 # in playlist mode, this can only filtering
64 # in library mode, this indicates all entries: (row, tag-value, indentation, next-row, expanded)*
65 fSongs
=None # filtered songs
66 numVisEntries
=None # number of entries that are visible (including when scrolling)
69 levels
=[] # levels from the groupBy in library-mode
70 groupByStr
='' # groupBy used in library-mode
76 numRows
=-1 # total number of rows that can be visible in 1 time
77 selRows
=None # ranges of selected rows: ( (startROw,endRow)* )
78 selIDs
=None # ranges of selected IDs: [ [startID,endID] ]
79 selMiscs
=None # array of indexes for selected non-songs in library mode
81 selMode
=False # currently in select mode?
82 resizeCol
=None # resizing a column?
83 clrID
=None # do we have to color a row with certain ID? [ID, color]
84 scrollMult
=1 # how many rows do we jump when scrolling by dragging
85 xOffset
=0 # offset for drawing. Is changed by hScrollbar
86 resizeColumn
=None # indicates this column should be recalculated
87 redrawID
=None # redraw this ID/row only
89 wgGfxAlbum
=QtSvg
.QSvgRenderer('gfx/gnome-cd.svg')
90 wgGfxArtist
=QtSvg
.QSvgRenderer('gfx/user_icon.svg')
93 def __init__(self
, parent
, name
, headers
, onDoubleClick
):
94 QtGui
.QWidget
.__init
__(self
, parent
)
95 self
.onDoubleClick
=onDoubleClick
97 # we receive an array of strings; we convert that to an array of (header, width)
99 # load the headers, and fetch from the settings the width and visibility
100 self
.headers
=map(lambda h
: [h
, int(settings
.get('l%s.%s.width'%(self
.name
,h
),250))
101 , settings
.get('l%s.%s.visible'%(self
.name
,h
),'1')=='1'], headers
)
102 self
.headers
.insert(0, ['id', 30, settings
.get('l%s.%s.visible'%(self
.name
,'id'),'0')=='1'])
107 self
.numVisEntries
=None
109 self
.resizeColumn
=None
113 self
.vScrollbar
=QtGui
.QScrollBar(QtCore
.Qt
.Vertical
, self
)
114 self
.vScrollbar
.setMinimum(0)
115 self
.vScrollbar
.setMaximum(1)
116 self
.vScrollbar
.setValue(0)
118 self
.hScrollbar
=QtGui
.QScrollBar(QtCore
.Qt
.Horizontal
, self
)
119 self
.hScrollbar
.setMinimum(0)
120 self
.hScrollbar
.setMaximum(1)
121 self
.hScrollbar
.setValue(0)
122 self
.hScrollbar
.setPageStep(200)
133 self
.connect(self
.vScrollbar
, QtCore
.SIGNAL('valueChanged(int)'),self
.onVScroll
)
134 self
.connect(self
.hScrollbar
, QtCore
.SIGNAL('valueChanged(int)'),self
.onHScroll
)
136 self
.setMouseTracking(True)
137 self
.setFocusPolicy(QtCore
.Qt
.TabFocus
or QtCore
.Qt
.ClickFocus
138 or QtCore
.Qt
.StrongFocus
or QtCore
.Qt
.WheelFocus
)
140 self
.setAttribute(QtCore
.Qt
.WA_OpaquePaintEvent
)
142 font
.setPixelSize(self
.fontSize
)
143 font
.setFamily('Liberation Sans') #TODO make this selectable
147 return QtCore
.QSize(10000,10000)
152 def customEvent(self
, event
):
153 if isinstance(event
, DoResize
):
154 self
.resizeEvent(None)
156 elif isinstance(event
, DoUpdate
):
159 Logger
.extended("wgSongList::Unknown event "+str(event
))
161 def setMode(self
, mode
, groupBy
=''):
167 self
.fSongs
=self
.songs
168 self
.numVisEntries
=len(self
.fSongs
)
169 elif mode
=='library':
170 self
.groupBy(groupBy
)
172 raise Exception('Unknown mode %' %(mode))
175 QtCore
.QCoreApplication
.postEvent(self
, DoResize())
177 def groupBy(self
, groupBy
, strFilter
=''):
178 self
.groupByStr
=groupBy
179 self
.levels
=groupBy
.split('/')
180 strFilter
=strFilter
.strip()
184 for i
in xrange(0,len(self
.levels
)):
185 formats
.append(format
.compile(self
.levels
[i
]))
186 # TODO also take l[1] etc into account?
188 xtra
={"album":U
, "artist":U
, "date":"", "genre":U
}
189 compare
=lambda left
, right
: cmp(\
190 formats
[0](format
.params(left
)).lower(), \
191 formats
[0](format
.params(right
)).lower() \
196 songs
=filter(lambda song
: song
.match(strFilter
), songs
)
197 songs
=sorted(songs
, compare
)
199 numLevels
=len(self
.levels
)
200 self
.fSongs
=[[0, 'dummy', 0, -1, False]]
202 # four levels ought to be enough for everyone
203 curLevels
=[[None,0], [None,0], [None,0], [None,0]] # contains the values of current levels
204 curLevel
=0 # current level we're in
205 parents
=[-1,-1,-1,-1] # index of parent
209 for level
in xrange(numLevels
):
210 # does the file have the required tag?
211 if not formats
[level
](format
.params(song
, {}, xtra
))==curLevels
[level
][LIB_ROW
]:
213 for i
in xrange(level
,numLevels
):
214 tagValue2
=formats
[i
](format
.params(song
, {}, xtra
))
216 self
.fSongs
[curLevels
[i
][1]][LIB_NEXTROW
]=finalRow
217 self
.fSongs
.append([row
, tagValue2
, i
, row
+1, 0, parents
[i
]])
221 curLevels
[i
]=[tagValue2
, row
]
223 self
.fSongs
.append([row
, song
, curLevel
, row
+1, 2, parents
[curLevel
]])
226 # update last entries' next-row of each level
227 # If we have e.g. artist/album, then the last artist and last album of that
228 # artist have to be repointed to the end of the list, else problems arise
229 # showing those entries ...
230 # indicate for each level whether we have processed that level yet
231 processed
=[False, False, False, False, False]
232 numFSongs
=len(self
.fSongs
)
233 for i
in xrange(numFSongs
-1,0,-1):
235 # look for last top-level entry
236 if song
[LIB_INDENT
]==0:
237 song
[LIB_NEXTROW
]=numFSongs
239 if processed
[song
[LIB_INDENT
]]==False:
240 song
[LIB_NEXTROW
]=numFSongs
241 processed
[song
[LIB_INDENT
]]=True
247 self
.numVisEntries
=len(filter(lambda entry
: entry
[LIB_INDENT
]==0, self
.fSongs
))
249 def updateSongs(self
, songs
):
250 """Update the displayed songs and clears selection."""
252 self
.numSongs
=len(songs
)
254 self
.setMode(self
.mode
, self
.groupByStr
)
257 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
259 def selectedSongs(self
):
260 """Returns the list of selected songs."""
262 if self
.mode
=='playlist':
263 cmp=lambda song
: song
._data
['id']>=range[0] and song
._data
['id']<=range[1]
264 elif self
.mode
=='library':
265 cmp=lambda song
: song
._data
['id']>=range[0] and song
._data
['id']<=range[1]
266 for range in self
.selIDs
:
267 # look for the songs in the current range
268 songs
=filter(cmp, self
.songs
)
273 def killFilters(self
):
276 while (len(self
._filters
)):
277 # wait 'till everything's cleared
282 # contains filters ready to be applied; only the top one will be used
284 def filter(self
, strFilter
):
285 """Filter songs according to $strFilter."""
287 self
._filters
.append(strFilter
)
288 strFilter
=self
._filters
[-1]
291 if self
.mode
=='playlist':
292 self
.fSongs
=filter(lambda song
: song
.match(strFilter
), self
.songs
)
293 self
.numVisEntries
=len(self
.fSongs
)
295 self
.groupBy(self
.groupByStr
, strFilter
)
297 # we might get here because self.songs is None
300 QtCore
.QCoreApplication
.postEvent(self
, DoResize())
302 def colorID(self
, id, clr
):
303 """Color the row which contains song with id $id $clr."""
307 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
309 def selectRow(self
, row
):
310 """Make $row the current selection."""
311 self
.selRows
=[[row
,row
]]
313 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
315 def showColumn(self
, column
, show
=True):
316 """Hide or show column $column."""
317 self
.headers
[column
][2]=show
321 def autoSizeColumn(self
, column
):
322 """Resizes column $column to fit the widest entry in the non-filtered songs."""
323 # we can't calculate it here, as retrieving the text-width can only
324 # be done in the paintEvent method ...
325 self
.resizeColumn
=column
327 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
329 def visibleSongs(self
):
330 """Get the songs currently visible."""
332 if self
.mode
=='playlist':
333 for row
in xrange(self
.topRow
, min(self
.numSongs
, self
.topRow
+self
.numRows
)-1):
334 ret
.append(self
.fSongs
[row
])
335 elif self
.mode
=='library':
336 # note that if everything is folded, there'll be no songs!
338 index
=self
.libFirstVisRowIndex()
340 while index
>=0 and index
<len(entries
) and count
<self
.numRows
:
341 entry
=self
.fSongs
[index
]
342 if isinstance(entry
[LIB_VALUE
], Song
):
343 ret
.append(entry
[LIB_VALUE
])
344 index
=self
.libIthVisRowIndex(index
)
348 def ensureVisible(self
, id):
349 """Make sure the song with $id is visible."""
350 if len(filter(lambda song
: song
.getID()==id, self
.visibleSongs())):
354 if self
.mode
=='playlist':
355 # playlist mode is simple: just hop to the song with id!
356 for song
in self
.fSongs
:
361 elif self
.mode
=='library':
362 # library mode is more complex: we must find out how many rows are visible,
363 # and expand the parents of the song, if necessary
364 indLevel
=0 # indicates what is the deepest level that is expanded for the current entry
365 # thus if current indent<=indLevel, then it is visible
366 for entry
in self
.fSongs
:
367 if entry
[LIB_EXPANDED
]==1:
368 indLevel
=max(entry
[LIB_INDENT
]+1, indLevel
)
369 elif entry
[LIB_EXPANDED
]==0:
370 indLevel
=min(entry
[LIB_INDENT
], indLevel
)
371 if entry
[LIB_INDENT
]<=indLevel
:
374 #print "%s -> %s"%(str(indLevel), str(entry))
375 if isinstance(entry
[LIB_VALUE
], Song
) and entry
[LIB_VALUE
].getID()==id:
377 # must be expanded in reverse order, else we will count too many
380 while entry
[LIB_PARENT
]>=0:
381 entry
=self
.fSongs
[entry
[LIB_PARENT
]]
382 parents
.append(entry
)
385 for parent
in parents
:
386 self
.libExpand(parent
)
391 self
.vScrollbar
.setValue(row
-self
.numRows
/2)
395 def onVScroll(self
, value
):
396 # 'if value<0' needed because minimum can be after init <0 at some point ...
398 if value
>self
.numVisEntries
:value
=self
.numVisEntries
403 def onHScroll(self
, value
):
404 self
.xOffset
=-self
.hScrollbar
.value()*2
407 def _pos2row(self
, pos
):
408 return int(pos
.y()/self
.lineHeight
)-1
409 def _row2entry(self
, row
):
413 if entry
[LIB_EXPANDED
]:
414 entry
=self
.fSongs
[entry
[LIB_ROW
]+1]
416 entry
=self
.fSongs
[entry
[LIB_NEXTROW
]]
422 def focusOutEvent(self
, event
):
424 def focusInEvent(self
, event
):
426 def wheelEvent(self
, event
):
427 if self
.vScrollbar
.isVisible():
429 numDegrees
=event
.delta() / 8
430 numSteps
=5*numDegrees
/15
431 self
.vScrollbar
.setValue(self
.vScrollbar
.value()-numSteps
)
433 def resizeEvent(self
, event
):
434 # max nr of rows shown
435 self
.numRows
=int(self
.height()/self
.lineHeight
)
437 # check vertical scrollbar
438 if self
.numRows
>self
.numVisEntries
:
439 self
.vScrollbar
.setVisible(False)
440 self
.vScrollbar
.setValue(0)
442 self
.vScrollbar
.setVisible(True)
443 self
.vScrollbar
.setPageStep(self
.numRows
-2)
444 self
.vScrollbar
.setMinimum(0)
445 self
.vScrollbar
.setMaximum(self
.numVisEntries
-self
.numRows
+1)
446 self
.vScrollbar
.resize(self
.scrollbarWidth
, self
.height()-self
.lineHeight
-1)
447 self
.vScrollbar
.move(self
.width()-self
.vScrollbar
.width()-1, self
.lineHeight
-1)
449 # check horizontal scrollbar
451 if self
.mode
=='playlist':
452 for hdr
in self
.headers
:
454 self
.scrollWidth
+=hdr
[1]
456 if self
.scrollWidth
>self
.width():
457 self
.hScrollbar
.setVisible(True)
458 self
.hScrollbar
.setMinimum(0)
459 self
.hScrollbar
.setMaximum((self
.scrollWidth
-self
.width())/2)
460 self
.hScrollbar
.resize(self
.width()-4, self
.scrollbarWidth
)
461 self
.hScrollbar
.move(2, self
.height()-self
.hScrollbar
.height()-1)
463 # some changes because the hScrollbar takes some vertical space ...
464 self
.vScrollbar
.resize(self
.vScrollbar
.width(), self
.vScrollbar
.height()-self
.lineHeight
)
465 self
.vScrollbar
.setMaximum(self
.vScrollbar
.maximum()+1)
469 self
.hScrollbar
.setVisible(False)
470 self
.hScrollbar
.setValue(0)
472 def libExpand(self
, entry
):
473 if entry
and entry
[LIB_EXPANDED
]==0:
474 self
.libToggle(entry
)
475 def libCollapse(self
, entry
):
476 if entry
and entry
[LIB_EXPANDED
]==1:
477 self
.libToggle(entry
)
479 def libToggle(self
, entry
):
480 """Toggles expanded state. Returns new state"""
481 expanded
=entry
[LIB_EXPANDED
]
483 # there was a '+' or a '-'!
484 entry
[LIB_EXPANDED
]=(expanded
+1)%2
485 ret
=entry
[LIB_EXPANDED
]
486 # we must find out how many entries have appeared/disappeard
488 visibles
=0 # how many new elements have appeared?
489 i
=entry
[LIB_ROW
]+1 # current element looking at
490 indLevel
=self
.fSongs
[i
][LIB_INDENT
]
491 while i
<=entry
[LIB_NEXTROW
]-1 and i
<len(self
.fSongs
):
492 entry2
=self
.fSongs
[i
]
493 if entry2
[LIB_EXPANDED
]==1:
494 indLevel
=max(entry2
[LIB_INDENT
]+1, indLevel
)
495 elif entry2
[LIB_EXPANDED
]==0:
496 indLevel
=min(entry2
[LIB_INDENT
], indLevel
)
497 if entry2
[LIB_INDENT
]<=indLevel
:
500 if entry2
[LIB_EXPANDED
]==0:
501 i
=entry2
[LIB_NEXTROW
]
507 self
.numVisEntries
+=mult
*visibles
508 self
.resizeEvent(None)
513 def mousePressEvent(self
, event
):
516 row
=self
._pos
2row
(pos
)
518 done
=False # indicates whether some action has been done or not
519 if self
.mode
=='playlist':
522 # we're clicking in the header!
526 # check if we're clicking between two columns, if so: resize mode!
527 for hdr
in self
.headers
:
534 elif self
.mode
=='library':
535 entry
=self
._row
2entry
(row
+self
.topRow
)
537 entry
=self
.fSongs
[len(self
.fSongs
)-1]
538 if entry
and pos
.x()>(1+entry
[LIB_INDENT
])*self
.indentation \
539 and pos
.x()<(1+entry
[LIB_INDENT
]+3/2)*self
.indentation
:
540 # we clicked in the margin, to expand or collapse
541 self
.libToggle(entry
)
548 if row
==-1 and self
.resizeCol
==None:
549 # we're not resizing, thus we can select all!
550 self
.selRows
=[[0, len(self
.fSongs
)]]
552 # we start selection mode
553 if self
.mode
=='playlist':
554 self
.selRows
=[[self
.topRow
+row
,self
.topRow
+row
]]
555 elif self
.mode
=='library':
556 self
.selRows
=[[entry
[LIB_ROW
], entry
[LIB_NEXTROW
]-1]]
561 def mouseMoveEvent(self
, event
):
563 row
=self
._pos
2row
(pos
)
565 # we're in selection mode
567 # scroll automatically when going out of the widget
571 jump
=int(self
.scrollMult
)*int(abs(pos
.y())/self
.lineHeight
)
572 self
.vScrollbar
.setValue(self
.vScrollbar
.value()-jump
)
574 elif row
>=self
.numRows
:
575 # scroll automatically when going out of the widget
577 jump
=int(self
.scrollMult
)*int(abs(self
.height()-pos
.y())/self
.lineHeight
)
578 self
.vScrollbar
.setValue(self
.vScrollbar
.value()+jump
)
579 row
=self
.numRows
-jump
581 # reset the scrollMultiplier
584 if self
.mode
=='playlist':
585 self
.selRows
[0][1]=row
+self
.topRow
586 elif self
.mode
=='library':
587 self
.selRows
[0][1]=self
.libIthVisRowIndex(self
.libIthVisRowIndex(0,self
.topRow
), row
)
589 elif self
.resizeCol
!=None:
591 # ohla, we're resizing a column!
593 # calculate where we are
594 for i
in xrange(self
.resizeCol
):
598 self
.headers
[self
.resizeCol
][1]=pos
.x()-prev
-self
.xOffset
599 # minimum width check?
600 if self
.headers
[self
.resizeCol
][1]<self
.minColumnWidth
:
601 self
.headers
[self
.resizeCol
][1]=self
.minColumnWidth
602 self
.resizeEvent(None)
605 def mouseReleaseEvent(self
, event
):
606 if self
.selMode
and len(self
.selRows
):
607 # we were selecting, but now we're done.
608 # We have to transform one range of rows
609 # into range of selected IDs
610 # The problem is that the list can be filtered, and that
611 # consequtive, visible rows aren't always directly
612 # consequtive in the unfiltered list.
613 self
.selMode
=False # exit selection mode
618 # loop over all rows that are selected
619 for entry
in fSongs
[min(self
.selRows
[0]):max(self
.selRows
[0])+1]:
621 if isinstance(entry
,Song
):
623 elif isinstance(entry
[LIB_VALUE
],Song
):
624 song
=entry
[LIB_VALUE
]
626 self
.selMiscs
.append(entry
[LIB_ROW
])
630 # is this song directly after the previous row?
631 if len(curRange
)==0 or curRange
[-1]+1==id:
634 ranges
.append(curRange
)
637 ranges
.append(curRange
)
642 self
.selIDs
.append([range[0], range[-1]])
645 elif self
.resizeCol
!=None:
646 # store this new width!
647 self
.saveColumnWidth(self
.resizeCol
)
648 # we're not resizing anymore!
651 def saveColumnWidth(self
, col
):
652 settings
.set('l%s.%s.width'%(self
.name
,self
.headers
[col
][0]), self
.headers
[col
][1])
653 def mouseDoubleClickEvent(self
, event
):
655 row
=self
._pos
2row
(pos
)
662 for hdr
in self
.headers
:
666 self
.autoSizeColumn(i
)
670 def _paintPlaylist(self
, p
):
674 lineHeight
=self
.lineHeight
679 if self
.vScrollbar
.isVisible():
680 width
-=self
.scrollbarWidth
682 if self
.resizeColumn
!=None:
683 # we're autoresizing!
684 # must be done here, because only here we can check the textwidth!
685 # This is because of limitations it can be only be done in paintEvent
686 hdr
=self
.headers
[self
.resizeColumn
][0]
687 w
=self
.minColumnWidth
688 # loop over all visible songs ...
689 for song
in self
.fSongs
:
690 rect
=p
.boundingRect(10,10,1,1, QtCore
.Qt
.AlignLeft
, song
.getTag(hdr
))
691 w
=max(rect
.width(), w
)
692 self
.headers
[self
.resizeColumn
][1]=w
+2*margin
693 self
.saveColumnWidth(self
.resizeColumn
)
694 self
.resizeColumn
=None
695 self
.resizeEvent(None)
696 if self
.redrawID
!=None:
697 # only update one row
699 for row
in xrange(self
.topRow
, min(self
.numVisEntries
, self
.topRow
+self
.numRows
)):
700 if self
.fSongs
[row
]._data
['id']==self
.redrawID
:
701 self
._paintPlaylistRow
(p
, row
, y
, width
)
708 p
.fillRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width(),lineHeight
), self
.palette
.brush(QPalette
.Button
))
709 p
.drawRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width()-1,lineHeight
-1))
710 x
=margin
+self
.xOffset
711 p
.setPen(self
.palette
.color(QPalette
.ButtonText
))
712 for hdr
in self
.headers
:
714 p
.drawText(x
, vmargin
, hdr
[1], lineHeight
, QtCore
.Qt
.AlignLeft
, hdr
[0])
716 p
.drawLine(QtCore
.QPoint(x
-margin
,0), QtCore
.QPoint(x
-margin
,lineHeight
))
722 for row
in xrange(self
.topRow
, min(self
.numVisEntries
, self
.topRow
+self
.numRows
)):
723 self
._paintPlaylistRow
(p
, row
, y
, width
)
726 # if we're short on songs, draw up the remaining area in background color
727 p
.fillRect(QtCore
.QRect(0,y
,width
,self
.height()-y
), QtGui
.QBrush(self
.clrBg
))
729 def _paintPlaylistRow(self
, p
, row
, y
, width
):
730 """Paint row $row on $p on height $y and with width $width."""
731 song
=self
.fSongs
[row
]
732 lineHeight
=self
.lineHeight
737 # determine color of row. Default is row-color, but can be overridden by
738 # (in this order): selection, special row color!
739 clr
=self
.colors
[row
%2] # background color of the row
740 clrTxt
= self
.palette
.color(QPalette
.Text
) # color of the printed text
749 # if values==[], it won't run!
751 # is selected if in range, which depends on the selection-mode
752 if checkID
>=min(range) and checkID
<=max(range):
754 clrTxt
= self
.palette
.color(QPalette
.HighlightedText
) # color of the printed text
755 # it has a VIP-status!
756 if id==int(self
.clrID
[0]):
757 clrTxt
= self
.palette
.color(QPalette
.HighlightedText
) # color of the printed text
760 # draw the row background
761 p
.fillRect(QtCore
.QRect(2, y
, width
-3, lineHeight
), QtGui
.QBrush(clr
))
763 # draw a subtile rectangle
764 p
.setPen(self
.palette
.color(QPalette
.Highlight
))
765 p
.drawRect(QtCore
.QRect(2, y
, width
-3, lineHeight
))
768 x
=margin
+self
.xOffset
769 for hdr
in self
.headers
:
771 # only if visible, duh!
772 # rectangle we're allowed to print in
773 text
=song
.getTag(hdr
[0])
774 if type(text
)!=str and type(text
)!=unicode:
776 rect
=p
.boundingRect(x
, y
, hdr
[1]-margin
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
778 p
.drawText(x
, y
+vmargin
, hdr
[1]-margin
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
779 if rect
.width()>hdr
[1]-margin
:
780 # print ellipsis, if necessary
781 p
.fillRect(x
+hdr
[1]-15,y
+1,15,lineHeight
-1, QtGui
.QBrush(clr
))
782 p
.drawText(x
+hdr
[1]-15,y
+vmargin
,15,lineHeight
-1, QtCore
.Qt
.AlignLeft
, "...")
784 p
.setPen(self
.palette
.color(QPalette
.Base
))
785 p
.drawLine(QtCore
.QPoint(x
-margin
,y
), QtCore
.QPoint(x
-margin
,y
+lineHeight
))
787 def libFirstVisRowIndex(self
):
788 """Returns the index of the first visible row in library mode."""
789 # if not in library mode, the unthinkable might happen! Wooo!
790 # TODO find better initial value
791 row
=0 # the visible rows we're visiting
792 index
=0 # what index does the current row have
795 while index
<len(entries
):
799 if entry
[LIB_EXPANDED
]==0:
800 index
=entry
[LIB_NEXTROW
]
805 def libIthVisRowIndex(self
, index
, i
=1):
806 """Returns the index of the $i-th next row after $index that is visible (or -1) in library mode."""
808 while i
>0 and index
<len(entries
):
810 entry
=self
.fSongs
[index
]
811 if entry
[LIB_EXPANDED
]==0:
814 index
=entry
[LIB_NEXTROW
]
822 for entry
in self
.fSongs
:
824 for i
in xrange(entry
[2]):
825 indent
=("%s ")%(indent)
826 print "%s%s" % (indent
, entry
)
828 def _paintLibrary(self
, p
):
831 lineHeight
=self
.lineHeight
836 p
.fillRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width(),lineHeight
), self
.palette
.brush(QPalette
.Button
))
837 p
.drawRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width()-1,lineHeight
-1))
838 p
.setPen(self
.palette
.color(QPalette
.ButtonText
))
839 p
.drawText(margin
, vmargin
, width
, lineHeight
, QtCore
.Qt
.AlignLeft
, self
.groupByStr
.replace('$', ''))
845 indent
=self
.indentation
846 index
=self
.libFirstVisRowIndex()
848 while index
<len(entries
) and y
<height
:
851 level
=entry
[LIB_INDENT
]
852 isSong
=isinstance(entry
[LIB_VALUE
], Song
)
856 text
="%s %s"%(entry
[LIB_VALUE
].getTrack(), entry
[LIB_VALUE
].getTitle())
858 if entry
[LIB_EXPANDED
]==1: prefix
='-'
859 elif entry
[LIB_EXPANDED
]==0: prefix
='+'
860 text
=entry
[LIB_VALUE
]
862 clr
=self
.colors
[row
%2] # background color of the row
863 clrTxt
= self
.palette
.color(QPalette
.Text
)
869 elif self
.selMode
==False and isSong
:
870 checkID
=entry
[LIB_VALUE
].getID()
873 # if values==[], then it won't run!
875 # is selected if in range, which depends on the selection-mode
876 if checkID
>=min(range) and checkID
<=max(range):
878 clrTxt
= self
.palette
.color(QPalette
.HighlightedText
)
880 for i
in self
.selMiscs
:
883 clrTxt
= self
.palette
.color(QPalette
.HighlightedText
)
885 # it has a VIP-status!
886 if isSong
and entry
[LIB_VALUE
].getID()==int(self
.clrID
[0]):
887 clrTxt
= self
.palette
.color(QPalette
.HighlightedText
)
890 left
=x
+indent
*(1+level
)
892 p
.fillRect(QtCore
.QRect(left
,y
,width
-3,lineHeight
), clr
)
894 p
.drawText(left
, top
, 15, lineHeight
, QtCore
.Qt
.AlignLeft
, prefix
)
895 p
.drawText(left
+15, top
, width
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
898 if level
<len(self
.levels
):
899 if self
.levels
[level
][0:len('$artist')]=='$artist':
901 elif self
.levels
[level
][0:len('$album')]=='$album':
905 obj
.render(p
, QtCore
.QRectF(indent
*level
+1,y
+1,lineHeight
-1,lineHeight
-1))
909 index
=self
.libIthVisRowIndex(index
)
913 _mutex
=QtCore
.QMutex()
915 def paintEvent(self
, event
):
924 p
=QtGui
.QPainter(self
)
926 # for the moment, redraw everything ...
927 p
.fillRect(QtCore
.QRect(0,0,self
.width(),self
.height()), QtGui
.QBrush(self
.clrBg
))
928 if self
.mode
=='playlist':
929 self
._paintPlaylist
(p
)
930 elif self
.mode
=='library':
931 self
._paintLibrary
(p
)
933 # draw a nice line around the widget!
934 p
.drawRect(QtCore
.QRect(0,0,self
.width()-1,self
.height()-1))
936 p
.drawRect(QtCore
.QRect(1,1,self
.width()-3,self
.height()-3))
938 p
.setPen(self
.palette
.color(QPalette
.Button
))
939 p
.drawRect(QtCore
.QRect(1,1,self
.width()-3,self
.height()-3))
944 if len(self
._filters
):
945 text
="Please wait while filtering ... (%i)"%(len(self
._filters
))
946 #text='%s - %s' % (self.selMiscs, '')
947 #text='%s - %s - %s' % (str(self.topRow), str(self.selRows), str(self.selIDs))
948 #text='%s - %s'%(str(self.topRow), str(self.numVisEntries))
950 r
=QtCore
.QRect(10,self
.height()-40,self
.width()-20,20)
951 p
.fillRect(r
, self
.palette
.brush(QPalette
.Base
))
952 p
.drawText(r
,QtCore
.Qt
.AlignLeft
, text
)