1 from PyQt4
import QtGui
, QtCore
, QtSvg
2 from PyQt4
.QtGui
import QPalette
5 from traceback
import print_exc
8 from clSong
import Song
9 from clSettings
import settings
12 # constants used for fSongs
17 LIB_EXPANDED
=4 # values: 0, 1 or 2 (==song)
20 class DoUpdate(QtCore
.QEvent
):
22 QtCore
.QEvent
.__init
__(self
, QtCore
.QEvent
.User
)
23 class DoResize(QtCore
.QEvent
):
25 QtCore
.QEvent
.__init
__(self
, QtCore
.QEvent
.User
)
27 class SongList(QtGui
.QWidget
):
28 """The SongList widget is a list optimized for displaying an array of songs, with filtering option."""
29 # CONFIGURATION VARIABLES
31 fontSize
=12 #TODO:make this selectable
32 " height of line in pxl"
33 lineHeight
= fontSize
+ 4
36 vmargin
=(lineHeight
-fontSize
)/2-1
37 " width of the vscrollbar"
39 " minimum column width"
41 " colors for alternating rows"
47 " indentation of hierarchy, in pixels"
48 indentation
=lineHeight
50 " what function to call when the list is double clicked"
53 mode
='playlist' # what mode is the songlist in? values: 'playlist', 'library'
54 " the headers: ( (header, width, visible)+ )"
56 songs
=None # original songs
57 numSongs
=None # number of songs
60 # in playlist mode, this can only filtering
61 # in library mode, this indicates all entries: (row, tag-value, indentation, next-row, expanded)*
62 fSongs
=None # filtered songs
63 numVisEntries
=None # number of entries that are visible (including when scrolling)
66 levels
=[] # levels from the groupBy in library-mode
67 groupByStr
='' # groupBy used in library-mode
73 numRows
=-1 # total number of rows that can be visible in 1 time
74 selRows
=None # ranges of selected rows: ( (startROw,endRow)* )
75 selIDs
=None # ranges of selected IDs: [ [startID,endID] ]
76 selMiscs
=None # array of indexes for selected non-songs in library mode
78 selMode
=False # currently in select mode?
79 resizeCol
=None # resizing a column?
80 clrID
=None # do we have to color a row with certain ID? [ID, color]
81 scrollMult
=1 # how many rows do we jump when scrolling by dragging
82 xOffset
=0 # offset for drawing. Is changed by hScrollbar
83 resizeColumn
=None # indicates this column should be recalculated
84 redrawID
=None # redraw this ID/row only
89 def __init__(self
, parent
, name
, headers
, onDoubleClick
):
90 QtGui
.QWidget
.__init
__(self
, parent
)
91 self
.onDoubleClick
=onDoubleClick
93 # we receive an array of strings; we convert that to an array of (header, width)
95 # load the headers, and fetch from the settings the width and visibility
96 self
.headers
=map(lambda h
: [h
, int(settings
.get('l%s.%s.width'%(self
.name
,h
),250))
97 , settings
.get('l%s.%s.visible'%(self
.name
,h
),'1')=='1'], headers
)
98 self
.headers
.insert(0, ['id', 30, settings
.get('l%s.%s.visible'%(self
.name
,'id'),'0')=='1'])
103 self
.numVisEntries
=None
105 self
.resizeColumn
=None
107 self
.colors
= [self
.palette().color(QPalette
.Base
), self
.palette().color(QPalette
.AlternateBase
)]
108 self
.clrSel
= self
.palette().color(QPalette
.Highlight
)
109 self
.clrBg
= self
.palette().color(QPalette
.Window
)
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
145 self
.wgGfxAlbum
=QtSvg
.QSvgRenderer('gfx/gnome-cd.svg')
146 self
.wgGfxArtist
=QtSvg
.QSvgRenderer('gfx/user_icon.svg')
149 return QtCore
.QSize(10000,10000)
154 def customEvent(self
, event
):
155 if isinstance(event
, DoResize
):
156 self
.resizeEvent(None)
158 elif isinstance(event
, DoUpdate
):
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 level
in self
.levels
:
185 formats
.append(format
.compile(level
))
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."""
253 self
.numSongs
= len(songs
)
257 self
.setMode(self
.mode
, self
.groupByStr
)
260 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
262 def selectedSongs(self
):
263 """Returns the list of selected songs."""
265 if self
.mode
=='playlist':
266 cmp=lambda song
: song
._data
['id']>=range[0] and song
._data
['id']<=range[1]
267 elif self
.mode
=='library':
268 cmp=lambda song
: song
._data
['id']>=range[0] and song
._data
['id']<=range[1]
269 for range in self
.selIDs
:
270 # look for the songs in the current range
271 songs
=filter(cmp, self
.songs
)
276 def killFilters(self
):
279 while (len(self
._filters
)):
280 # wait 'till everything's cleared
285 # contains filters ready to be applied; only the top one will be used
287 def filter(self
, strFilter
):
288 """Filter songs according to $strFilter."""
290 self
._filters
.append(strFilter
)
291 strFilter
=self
._filters
[-1]
294 if self
.mode
=='playlist':
295 self
.fSongs
=filter(lambda song
: song
.match(strFilter
), self
.songs
)
296 self
.numVisEntries
=len(self
.fSongs
)
298 self
.groupBy(self
.groupByStr
, strFilter
)
300 # we might get here because self.songs is None
303 QtCore
.QCoreApplication
.postEvent(self
, DoResize())
305 def colorID(self
, id, clr
):
306 """Color the row which contains song with id $id $clr."""
310 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
312 def selectRow(self
, row
):
313 """Make $row the current selection."""
314 self
.selRows
=[[row
,row
]]
316 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
318 def showColumn(self
, column
, show
=True):
319 """Hide or show column $column."""
320 self
.headers
[column
][2]=show
324 def autoSizeColumn(self
, column
):
325 """Resizes column $column to fit the widest entry in the non-filtered songs."""
326 # we can't calculate it here, as retrieving the text-width can only
327 # be done in the paintEvent method ...
328 self
.resizeColumn
=column
330 QtCore
.QCoreApplication
.postEvent(self
, DoUpdate())
332 def visibleSongs(self
):
333 """Get the songs currently visible."""
335 if self
.mode
=='playlist':
336 for row
in xrange(self
.topRow
, min(self
.numSongs
, self
.topRow
+self
.numRows
)-1):
337 ret
.append(self
.fSongs
[row
])
338 elif self
.mode
=='library':
339 # note that if everything is folded, there'll be no songs!
341 index
=self
.libFirstVisRowIndex()
343 while index
>=0 and index
<len(entries
) and count
<self
.numRows
:
344 entry
=self
.fSongs
[index
]
345 if isinstance(entry
[LIB_VALUE
], Song
):
346 ret
.append(entry
[LIB_VALUE
])
347 index
=self
.libIthVisRowIndex(index
)
351 def ensureVisible(self
, id):
352 """Make sure the song with $id is visible."""
353 if len(filter(lambda song
: song
.getID()==id, self
.visibleSongs())):
357 if self
.mode
=='playlist':
358 # playlist mode is simple: just hop to the song with id!
359 for song
in self
.fSongs
:
364 elif self
.mode
=='library':
365 # library mode is more complex: we must find out how many rows are visible,
366 # and expand the parents of the song, if necessary
367 indLevel
=0 # indicates what is the deepest level that is expanded for the current entry
368 # thus if current indent<=indLevel, then it is visible
369 for entry
in self
.fSongs
:
370 if entry
[LIB_EXPANDED
]==1:
371 indLevel
=max(entry
[LIB_INDENT
]+1, indLevel
)
372 elif entry
[LIB_EXPANDED
]==0:
373 indLevel
=min(entry
[LIB_INDENT
], indLevel
)
374 if entry
[LIB_INDENT
]<=indLevel
:
377 #print "%s -> %s"%(str(indLevel), str(entry))
378 if isinstance(entry
[LIB_VALUE
], Song
) and entry
[LIB_VALUE
].getID()==id:
380 # must be expanded in reverse order, else we will count too many
383 while entry
[LIB_PARENT
]>=0:
384 entry
=self
.fSongs
[entry
[LIB_PARENT
]]
385 parents
.append(entry
)
388 for parent
in parents
:
389 self
.libExpand(parent
)
394 self
.vScrollbar
.setValue(row
-self
.numRows
/2)
398 def onVScroll(self
, value
):
399 # 'if value<0' needed because minimum can be after init <0 at some point ...
401 if value
>self
.numVisEntries
:value
=self
.numVisEntries
406 def onHScroll(self
, value
):
407 self
.xOffset
=-self
.hScrollbar
.value()*2
410 def _pos2row(self
, pos
):
411 return int(pos
.y()/self
.lineHeight
)-1
412 def _row2entry(self
, row
):
416 if entry
[LIB_EXPANDED
]:
417 entry
=self
.fSongs
[entry
[LIB_ROW
]+1]
419 entry
=self
.fSongs
[entry
[LIB_NEXTROW
]]
425 def focusOutEvent(self
, event
):
427 def focusInEvent(self
, event
):
429 def wheelEvent(self
, event
):
430 if self
.vScrollbar
.isVisible():
432 numDegrees
=event
.delta() / 8
433 numSteps
=5*numDegrees
/15
434 self
.vScrollbar
.setValue(self
.vScrollbar
.value()-numSteps
)
436 def resizeEvent(self
, event
):
437 # max nr of rows shown
438 self
.numRows
=int(self
.height()/self
.lineHeight
)
440 # check vertical scrollbar
441 if self
.numRows
>self
.numVisEntries
:
442 self
.vScrollbar
.setVisible(False)
443 self
.vScrollbar
.setValue(0)
445 self
.vScrollbar
.setVisible(True)
446 self
.vScrollbar
.setPageStep(self
.numRows
-2)
447 self
.vScrollbar
.setMinimum(0)
448 self
.vScrollbar
.setMaximum(self
.numVisEntries
-self
.numRows
+1)
449 self
.vScrollbar
.resize(self
.scrollbarWidth
, self
.height()-self
.lineHeight
-1)
450 self
.vScrollbar
.move(self
.width()-self
.vScrollbar
.width()-1, self
.lineHeight
-1)
452 # check horizontal scrollbar
454 if self
.mode
=='playlist':
455 for hdr
in self
.headers
:
457 self
.scrollWidth
+=hdr
[1]
459 if self
.scrollWidth
>self
.width():
460 self
.hScrollbar
.setVisible(True)
461 self
.hScrollbar
.setMinimum(0)
462 self
.hScrollbar
.setMaximum((self
.scrollWidth
-self
.width())/2)
463 self
.hScrollbar
.resize(self
.width()-4, self
.scrollbarWidth
)
464 self
.hScrollbar
.move(2, self
.height()-self
.hScrollbar
.height()-1)
466 # some changes because the hScrollbar takes some vertical space ...
467 self
.vScrollbar
.resize(self
.vScrollbar
.width(), self
.vScrollbar
.height()-self
.lineHeight
)
468 self
.vScrollbar
.setMaximum(self
.vScrollbar
.maximum()+1)
472 self
.hScrollbar
.setVisible(False)
473 self
.hScrollbar
.setValue(0)
475 def libExpand(self
, entry
):
476 if entry
and entry
[LIB_EXPANDED
]==0:
477 self
.libToggle(entry
)
478 def libCollapse(self
, entry
):
479 if entry
and entry
[LIB_EXPANDED
]==1:
480 self
.libToggle(entry
)
482 def libToggle(self
, entry
):
483 """Toggles expanded state. Returns new state"""
484 expanded
=entry
[LIB_EXPANDED
]
486 # there was a '+' or a '-'!
487 entry
[LIB_EXPANDED
]=(expanded
+1)%2
488 ret
=entry
[LIB_EXPANDED
]
489 # we must find out how many entries have appeared/disappeard
491 visibles
=0 # how many new elements have appeared?
492 i
=entry
[LIB_ROW
]+1 # current element looking at
493 indLevel
=self
.fSongs
[i
][LIB_INDENT
]
494 while i
<=entry
[LIB_NEXTROW
]-1 and i
<len(self
.fSongs
):
495 entry2
=self
.fSongs
[i
]
496 if entry2
[LIB_EXPANDED
]==1:
497 indLevel
=max(entry2
[LIB_INDENT
]+1, indLevel
)
498 elif entry2
[LIB_EXPANDED
]==0:
499 indLevel
=min(entry2
[LIB_INDENT
], indLevel
)
500 if entry2
[LIB_INDENT
]<=indLevel
:
503 if entry2
[LIB_EXPANDED
]==0:
504 i
=entry2
[LIB_NEXTROW
]
510 self
.numVisEntries
+=mult
*visibles
511 self
.resizeEvent(None)
516 def mousePressEvent(self
, event
):
519 row
=self
._pos
2row
(pos
)
521 done
=False # indicates whether some action has been done or not
522 if self
.mode
=='playlist':
525 # we're clicking in the header!
529 # check if we're clicking between two columns, if so: resize mode!
530 for hdr
in self
.headers
:
537 elif self
.mode
=='library':
538 entry
=self
._row
2entry
(row
+self
.topRow
)
540 entry
=self
.fSongs
[len(self
.fSongs
)-1]
541 if entry
and pos
.x()>(1+entry
[LIB_INDENT
])*self
.indentation \
542 and pos
.x()<(1+entry
[LIB_INDENT
]+3/2)*self
.indentation
:
543 # we clicked in the margin, to expand or collapse
544 self
.libToggle(entry
)
551 if row
==-1 and self
.resizeCol
==None:
552 # we're not resizing, thus we can select all!
553 self
.selRows
=[[0, len(self
.fSongs
)]]
555 # we start selection mode
556 if self
.mode
=='playlist':
557 self
.selRows
=[[self
.topRow
+row
,self
.topRow
+row
]]
558 elif self
.mode
=='library':
559 self
.selRows
=[[entry
[LIB_ROW
], entry
[LIB_NEXTROW
]-1]]
564 def mouseMoveEvent(self
, event
):
566 row
=self
._pos
2row
(pos
)
568 # we're in selection mode
570 # scroll automatically when going out of the widget
574 jump
=int(self
.scrollMult
)*int(abs(pos
.y())/self
.lineHeight
)
575 self
.vScrollbar
.setValue(self
.vScrollbar
.value()-jump
)
577 elif row
>=self
.numRows
:
578 # scroll automatically when going out of the widget
580 jump
=int(self
.scrollMult
)*int(abs(self
.height()-pos
.y())/self
.lineHeight
)
581 self
.vScrollbar
.setValue(self
.vScrollbar
.value()+jump
)
582 row
=self
.numRows
-jump
584 # reset the scrollMultiplier
587 if self
.mode
=='playlist':
588 self
.selRows
[0][1]=row
+self
.topRow
589 elif self
.mode
=='library':
590 self
.selRows
[0][1]=self
.libIthVisRowIndex(self
.libIthVisRowIndex(0,self
.topRow
), row
)
592 elif self
.resizeCol
!=None:
594 # ohla, we're resizing a column!
596 # calculate where we are
597 for i
in xrange(self
.resizeCol
):
601 self
.headers
[self
.resizeCol
][1]=pos
.x()-prev
-self
.xOffset
602 # minimum width check?
603 if self
.headers
[self
.resizeCol
][1]<self
.minColumnWidth
:
604 self
.headers
[self
.resizeCol
][1]=self
.minColumnWidth
605 self
.resizeEvent(None)
608 def mouseReleaseEvent(self
, event
):
609 if self
.selMode
and len(self
.selRows
):
610 # we were selecting, but now we're done.
611 # We have to transform one range of rows
612 # into range of selected IDs
613 # The problem is that the list can be filtered, and that
614 # consequtive, visible rows aren't always directly
615 # consequtive in the unfiltered list.
616 self
.selMode
=False # exit selection mode
621 # loop over all rows that are selected
622 for entry
in fSongs
[min(self
.selRows
[0]):max(self
.selRows
[0])+1]:
624 if isinstance(entry
,Song
):
626 elif isinstance(entry
[LIB_VALUE
],Song
):
627 song
=entry
[LIB_VALUE
]
629 self
.selMiscs
.append(entry
[LIB_ROW
])
633 # is this song directly after the previous row?
634 if len(curRange
)==0 or curRange
[-1]+1==id:
637 ranges
.append(curRange
)
640 ranges
.append(curRange
)
645 self
.selIDs
.append([range[0], range[-1]])
648 elif self
.resizeCol
!=None:
649 # store this new width!
650 self
.saveColumnWidth(self
.resizeCol
)
651 # we're not resizing anymore!
654 def saveColumnWidth(self
, col
):
655 settings
.set('l%s.%s.width'%(self
.name
,self
.headers
[col
][0]), self
.headers
[col
][1])
656 def mouseDoubleClickEvent(self
, event
):
658 row
=self
._pos
2row
(pos
)
665 for hdr
in self
.headers
:
669 self
.autoSizeColumn(i
)
673 def _paintPlaylist(self
, p
):
677 lineHeight
=self
.lineHeight
682 if self
.vScrollbar
.isVisible():
683 width
-=self
.scrollbarWidth
685 if self
.resizeColumn
!=None:
686 # we're autoresizing!
687 # must be done here, because only here we can check the textwidth!
688 # This is because of limitations it can be only be done in paintEvent
689 hdr
=self
.headers
[self
.resizeColumn
][0]
690 w
=self
.minColumnWidth
691 # loop over all visible songs ...
692 for song
in self
.fSongs
:
693 rect
=p
.boundingRect(10,10,1,1, QtCore
.Qt
.AlignLeft
, song
.getTag(hdr
))
694 w
=max(rect
.width(), w
)
695 self
.headers
[self
.resizeColumn
][1]=w
+2*margin
696 self
.saveColumnWidth(self
.resizeColumn
)
697 self
.resizeColumn
=None
698 self
.resizeEvent(None)
699 if self
.redrawID
!=None:
700 # only update one row
702 for row
in xrange(self
.topRow
, min(self
.numVisEntries
, self
.topRow
+self
.numRows
)):
703 if self
.fSongs
[row
]._data
['id']==self
.redrawID
:
704 self
._paintPlaylistRow
(p
, row
, y
, width
)
711 p
.fillRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width(),lineHeight
), self
.palette().brush(QPalette
.Button
))
712 p
.drawRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width()-1,lineHeight
-1))
713 x
=margin
+self
.xOffset
714 p
.setPen(self
.palette().color(QPalette
.ButtonText
))
715 for hdr
in self
.headers
:
717 p
.drawText(x
, vmargin
, hdr
[1], lineHeight
, QtCore
.Qt
.AlignLeft
, hdr
[0])
719 p
.drawLine(QtCore
.QPoint(x
-margin
,0), QtCore
.QPoint(x
-margin
,lineHeight
))
725 for row
in xrange(self
.topRow
, min(self
.numVisEntries
, self
.topRow
+self
.numRows
)):
726 self
._paintPlaylistRow
(p
, row
, y
, width
)
729 # if we're short on songs, draw up the remaining area in background color
730 p
.fillRect(QtCore
.QRect(0,y
,width
,self
.height()-y
), QtGui
.QBrush(self
.clrBg
))
732 def _paintPlaylistRow(self
, p
, row
, y
, width
):
733 """Paint row $row on $p on height $y and with width $width."""
734 song
=self
.fSongs
[row
]
735 lineHeight
=self
.lineHeight
740 # determine color of row. Default is row-color, but can be overridden by
741 # (in this order): selection, special row color!
742 clr
=self
.colors
[row
%2] # background color of the row
743 clrTxt
= self
.palette().color(QPalette
.Text
) # color of the printed text
752 # if values==[], it won't run!
754 # is selected if in range, which depends on the selection-mode
755 if checkID
>=min(range) and checkID
<=max(range):
757 clrTxt
= self
.palette().color(QPalette
.HighlightedText
) # color of the printed text
758 # it has a VIP-status!
759 if id==int(self
.clrID
[0]):
760 clrTxt
= self
.palette().color(QPalette
.HighlightedText
) # color of the printed text
763 # draw the row background
764 p
.fillRect(QtCore
.QRect(2, y
, width
-3, lineHeight
), QtGui
.QBrush(clr
))
766 # draw a subtile rectangle
767 p
.setPen(self
.palette().color(QPalette
.Highlight
))
768 p
.drawRect(QtCore
.QRect(2, y
, width
-3, lineHeight
))
771 x
=margin
+self
.xOffset
772 for hdr
in self
.headers
:
774 # only if visible, duh!
775 # rectangle we're allowed to print in
776 text
=song
.getTag(hdr
[0])
777 if type(text
)!=str and type(text
)!=unicode:
779 rect
=p
.boundingRect(x
, y
, hdr
[1]-margin
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
781 p
.drawText(x
, y
+vmargin
, hdr
[1]-margin
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
782 if rect
.width()>hdr
[1]-margin
:
783 # print ellipsis, if necessary
784 p
.fillRect(x
+hdr
[1]-15,y
+1,15,lineHeight
-1, QtGui
.QBrush(clr
))
785 p
.drawText(x
+hdr
[1]-15,y
+vmargin
,15,lineHeight
-1, QtCore
.Qt
.AlignLeft
, "...")
787 p
.setPen(self
.palette().color(QPalette
.Base
))
788 p
.drawLine(QtCore
.QPoint(x
-margin
,y
), QtCore
.QPoint(x
-margin
,y
+lineHeight
))
790 def libFirstVisRowIndex(self
):
791 """Returns the index of the first visible row in library mode."""
792 # if not in library mode, the unthinkable might happen! Wooo!
793 # TODO find better initial value
794 row
=0 # the visible rows we're visiting
795 index
=0 # what index does the current row have
798 while index
<len(entries
):
802 if entry
[LIB_EXPANDED
]==0:
803 index
=entry
[LIB_NEXTROW
]
808 def libIthVisRowIndex(self
, index
, i
=1):
809 """Returns the index of the $i-th next row after $index that is visible (or -1) in library mode."""
811 while i
>0 and index
<len(entries
):
813 entry
=self
.fSongs
[index
]
814 if entry
[LIB_EXPANDED
]==0:
817 index
=entry
[LIB_NEXTROW
]
823 def _paintLibrary(self
, p
):
826 lineHeight
=self
.lineHeight
831 p
.fillRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width(),lineHeight
), self
.palette().brush(QPalette
.Button
))
832 p
.drawRect(QtCore
.QRect(0,0,width
+self
.vScrollbar
.width()-1,lineHeight
-1))
833 p
.setPen(self
.palette().color(QPalette
.ButtonText
))
834 p
.drawText(margin
, vmargin
, width
, lineHeight
, QtCore
.Qt
.AlignLeft
, self
.groupByStr
.replace('$', ''))
840 indent
=self
.indentation
841 index
=self
.libFirstVisRowIndex()
843 while index
<len(entries
) and y
<height
:
846 level
=entry
[LIB_INDENT
]
847 isSong
=isinstance(entry
[LIB_VALUE
], Song
)
851 text
="%s %s"%(entry
[LIB_VALUE
].getTrack(), entry
[LIB_VALUE
].getTitle())
853 if entry
[LIB_EXPANDED
]==1: prefix
='-'
854 elif entry
[LIB_EXPANDED
]==0: prefix
='+'
855 text
=entry
[LIB_VALUE
]
857 clr
=self
.colors
[row
%2] # background color of the row
858 clrTxt
= self
.palette().color(QPalette
.Text
)
864 elif self
.selMode
==False and isSong
:
865 checkID
=entry
[LIB_VALUE
].getID()
868 # if values==[], then it won't run!
870 # is selected if in range, which depends on the selection-mode
871 if checkID
>=min(range) and checkID
<=max(range):
873 clrTxt
= self
.palette().color(QPalette
.HighlightedText
)
875 for i
in self
.selMiscs
:
878 clrTxt
= self
.palette().color(QPalette
.HighlightedText
)
880 # it has a VIP-status!
881 if isSong
and entry
[LIB_VALUE
].getID()==int(self
.clrID
[0]):
882 clrTxt
= self
.palette().color(QPalette
.HighlightedText
)
885 left
=x
+indent
*(1+level
)
887 p
.fillRect(QtCore
.QRect(left
,y
,width
-3,lineHeight
), clr
)
889 p
.drawText(left
, top
, 15, lineHeight
, QtCore
.Qt
.AlignLeft
, prefix
)
890 p
.drawText(left
+15, top
, width
, lineHeight
, QtCore
.Qt
.AlignLeft
, text
)
893 if level
<len(self
.levels
):
894 if self
.levels
[level
][0:len('$artist')]=='$artist':
896 elif self
.levels
[level
][0:len('$album')]=='$album':
900 obj
.render(p
, QtCore
.QRectF(indent
*level
+1,y
+1,lineHeight
-1,lineHeight
-1))
904 index
=self
.libIthVisRowIndex(index
)
908 _mutex
=QtCore
.QMutex()
910 def paintEvent(self
, event
):
919 p
=QtGui
.QPainter(self
)
921 # for the moment, redraw everything ...
922 p
.fillRect(QtCore
.QRect(0,0,self
.width(),self
.height()), QtGui
.QBrush(self
.clrBg
))
923 if self
.mode
=='playlist':
924 self
._paintPlaylist
(p
)
925 elif self
.mode
=='library':
926 self
._paintLibrary
(p
)
928 # draw a nice line around the widget!
929 p
.drawRect(QtCore
.QRect(0,0,self
.width()-1,self
.height()-1))
931 p
.drawRect(QtCore
.QRect(1,1,self
.width()-3,self
.height()-3))
933 p
.setPen(self
.palette().color(QPalette
.Button
))
934 p
.drawRect(QtCore
.QRect(1,1,self
.width()-3,self
.height()-3))
939 if len(self
._filters
):
940 text
="Please wait while filtering ... (%i)"%(len(self
._filters
))
941 #text='%s - %s' % (self.selMiscs, '')
942 #text='%s - %s - %s' % (str(self.topRow), str(self.selRows), str(self.selIDs))
943 #text='%s - %s'%(str(self.topRow), str(self.numVisEntries))
945 r
=QtCore
.QRect(10,self
.height()-40,self
.width()-20,20)
946 p
.fillRect(r
, self
.palette().brush(QPalette
.Base
))
947 p
.drawText(r
,QtCore
.Qt
.AlignLeft
, text
)