예제 #1
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self.filterhistory = []
        self.lastdirectory = None # last used directory in file dialogs
        self.nowfiltering = False # whether the filter is currently processing or no
        self.nowgroupfiltering = False # whether the group filter is -||-
        #self.found = -1 # total in the selected group / all tracks
        #self.new = -1 # total new in the *shown* tracks
        #self.total = -1
        self.prevgroup = None

        # icons
        icn = QtGui.QIcon(":/regaudio.icon.48.png")
        icn.addFile(":/regaudio.icon.32.png")
        icn.addFile(":/regaudio.icon.24.png")
        icn.addFile(":/regaudio.icon.16.png")
        self.setWindowIcon(icn)

        # show a warning in case of no configuration
        self.showDBWarning()

        # setup status
        self.statusline = QtGui.QLabel()
        self.statusBar().addWidget(self.statusline)

        # models
        self.datamdl = model.Model()
        self.stats = self.datamdl.stats # share statistics
        self.tracks = TracksModel(self.datamdl.getTracks())
        self.groups = GroupsModel(self.datamdl.getGroups())

        #connect feedback first
        self.tracks.feedback.connect(self.modelfeedback)
        self.groups.feedback.connect(self.modelfeedback)

        # set table
        self.trackstable.setModel(self.tracks)
        self.trackstable.setItemDelegate(TracksDelegate(self.tracks))
        self.trackstable.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.trackstable.customContextMenuRequested.connect(self.tracksContextMenu)
        self.trackstable.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked |
            QtGui.QAbstractItemView.SelectedClicked |
            QtGui.QAbstractItemView.EditKeyPressed )
        self.trackstable.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)

        # set groups
        self.filtergroups = QtGui.QSortFilterProxyModel(self)
        self.filtergroups.setSourceModel(self.groups)
        self.filtergroups.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.groupbox.setModel(self.filtergroups)
        self.groupbox.currentIndexChanged.connect(self.groupSet)
        self.groupallbtn.clicked.connect(self.actionGroupAllTracks.activate)
        self.groupfavouritebtn.clicked.connect(self.actionGroupFavourite.activate)
        self.groupnewbtn.setMenu(self.menuGroupNewImport)
        self.groupnewbtn.clicked.connect(self.groupnewbtn.showMenu)
        self.groupdeletebtn.clicked.connect(self.actionGroupDelete.activate)
        self.grouprenamebtn.clicked.connect(self.actionGroupRename.activate)
        self.groupfilterbtn.toggled.connect(self.groupFilterBool)
        self.groupfilterline.editingFinished.connect(self.groupFilter)
        self.groupfilterline.setEnabled(False)
        self.groupfilterline.setVisible(False)

        # set paging
        self.pagemaxtracksline.setText("100")
        self.pagepageline.setText("1")
        self.pagemaxtracksline.setValidator(QtGui.QIntValidator())
        self.pagepageline.setValidator(QtGui.QIntValidator())
        self.pagemaxtracksline.editingFinished.connect(self.filterSet)
        self.pagepageline.editingFinished.connect(self.filterSet)
        self.pagenextbtn.clicked.connect(self.pagenext)
        self.pageprevbtn.clicked.connect(self.pageprev)

        #connect filter
        self.filterline.editingFinished.connect(self.filterSet)
        self.actionFilterReset.triggered.connect(self.filterSetTo)
        self.menuFilterHistory.aboutToShow.connect(self.filterHistoryUpdateMenu)
        self.filterreset.clicked.connect(self.actionFilterReset.activate)
        self.filterrecall.setMenu(self.menuFilterHistory)        
        self.filterrecall.clicked.connect(self.filterrecall.showMenu)                

        #connect actions
        self.actionTrackDelete.triggered.connect(self.trackDelete)
        self.actionTrackNew.triggered.connect(self.trackNew)
        self.actionTrackToggleNew.triggered.connect(self.trackToggleNew)
        self.actionTrackDetachedCopy.triggered.connect(self.trackDetachedCopy)
        receivermm = lambda : self.groupimport(what="mm")
        self.actionImport.triggered.connect(receivermm)
        self.actionQuit.triggered.connect(self.quit)

        self.actionGroupAllTracks.triggered.connect(self.groupAllTracks)
        self.actionGroupFavourite.triggered.connect(self.groupFavorite)
        self.actionGroupNew.triggered.connect(self.groupNew)

        receivercue = lambda : self.groupimport(what="cue")
        self.actionGroupImportCue.triggered.connect(receivercue)#self.groupImportCue)
        receiverdir = lambda : self.groupimport(what="dir")
        self.actionGroupImportDirectory.triggered.connect(receiverdir)#self.groupImportDir)
        self.actionGroupDelete.triggered.connect(self.groupDelete)
        self.actionGroupRename.triggered.connect(self.groupRename)

        self.menuTrack.aboutToShow.connect(self.updateMenuTrack)
        self.menuTrackMerge.aboutToShow.connect(self.updateMenuTrackMerge)
        self.menuTrackInGroups.aboutToShow.connect(self.updateMenuTrackInGroups)
        self.menuTrackAddToGroup.aboutToShow.connect(self.updateMenuTrackAddToGroup)
        self.menuTrackRelink.aboutToShow.connect(self.updateMenuTrackRelink)

        for act in [["0", lambda : self.trackRating(0), "`"],
                   ["1", lambda : self.trackRating(1), "1"],
                   ["2", lambda : self.trackRating(2), "2"],
                   ["3", lambda : self.trackRating(3), "3"],
                   ["4", lambda : self.trackRating(4), "4"],
                   ["5", lambda : self.trackRating(5), "5"],
                   ["6", lambda : self.trackRating(6), "6"],
                   ["7", lambda : self.trackRating(7), "7"],
                   ["8", lambda : self.trackRating(8), "8"],
                   ["9", lambda : self.trackRating(9), "9"],
                   ["10", lambda : self.trackRating(10), "0"]]:
                action = QtGui.QAction(self)
                action.setText(act[0])
                action.triggered.connect(act[1])
                if len(act) == 3:
                    action.setShortcut(QtGui.QKeySequence.fromString(act[2]))
                self.menuRating.addAction(action)
                self.trackstable.addAction(action)

        # this feeds all the default settings to the model and read the data from db
        self.filterSet()
        # FIXME the following causes that the model is refreshed three times

        self.tracks.sortingreallyenabled = False
        self.trackstable.sortByColumn(0,QtCore.Qt.AscendingOrder)
        self.trackstable.setSortingEnabled(True)
        self.tracks.sortingreallyenabled = True

        self.statusupdate()
예제 #2
0
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self.filterhistory = []
        self.lastdirectory = None # last used directory in file dialogs
        self.nowfiltering = False # whether the filter is currently processing or no
        self.nowgroupfiltering = False # whether the group filter is -||-
        #self.found = -1 # total in the selected group / all tracks
        #self.new = -1 # total new in the *shown* tracks
        #self.total = -1
        self.prevgroup = None

        # icons
        icn = QtGui.QIcon(":/regaudio.icon.48.png")
        icn.addFile(":/regaudio.icon.32.png")
        icn.addFile(":/regaudio.icon.24.png")
        icn.addFile(":/regaudio.icon.16.png")
        self.setWindowIcon(icn)

        # show a warning in case of no configuration
        self.showDBWarning()

        # setup status
        self.statusline = QtGui.QLabel()
        self.statusBar().addWidget(self.statusline)

        # models
        self.datamdl = model.Model()
        self.stats = self.datamdl.stats # share statistics
        self.tracks = TracksModel(self.datamdl.getTracks())
        self.groups = GroupsModel(self.datamdl.getGroups())

        #connect feedback first
        self.tracks.feedback.connect(self.modelfeedback)
        self.groups.feedback.connect(self.modelfeedback)

        # set table
        self.trackstable.setModel(self.tracks)
        self.trackstable.setItemDelegate(TracksDelegate(self.tracks))
        self.trackstable.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.trackstable.customContextMenuRequested.connect(self.tracksContextMenu)
        self.trackstable.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked |
            QtGui.QAbstractItemView.SelectedClicked |
            QtGui.QAbstractItemView.EditKeyPressed )
        self.trackstable.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)

        # set groups
        self.filtergroups = QtGui.QSortFilterProxyModel(self)
        self.filtergroups.setSourceModel(self.groups)
        self.filtergroups.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.groupbox.setModel(self.filtergroups)
        self.groupbox.currentIndexChanged.connect(self.groupSet)
        self.groupallbtn.clicked.connect(self.actionGroupAllTracks.activate)
        self.groupfavouritebtn.clicked.connect(self.actionGroupFavourite.activate)
        self.groupnewbtn.setMenu(self.menuGroupNewImport)
        self.groupnewbtn.clicked.connect(self.groupnewbtn.showMenu)
        self.groupdeletebtn.clicked.connect(self.actionGroupDelete.activate)
        self.grouprenamebtn.clicked.connect(self.actionGroupRename.activate)
        self.groupfilterbtn.toggled.connect(self.groupFilterBool)
        self.groupfilterline.editingFinished.connect(self.groupFilter)
        self.groupfilterline.setEnabled(False)
        self.groupfilterline.setVisible(False)

        # set paging
        self.pagemaxtracksline.setText("100")
        self.pagepageline.setText("1")
        self.pagemaxtracksline.setValidator(QtGui.QIntValidator())
        self.pagepageline.setValidator(QtGui.QIntValidator())
        self.pagemaxtracksline.editingFinished.connect(self.filterSet)
        self.pagepageline.editingFinished.connect(self.filterSet)
        self.pagenextbtn.clicked.connect(self.pagenext)
        self.pageprevbtn.clicked.connect(self.pageprev)

        #connect filter
        self.filterline.editingFinished.connect(self.filterSet)
        self.actionFilterReset.triggered.connect(self.filterSetTo)
        self.menuFilterHistory.aboutToShow.connect(self.filterHistoryUpdateMenu)
        self.filterreset.clicked.connect(self.actionFilterReset.activate)
        self.filterrecall.setMenu(self.menuFilterHistory)        
        self.filterrecall.clicked.connect(self.filterrecall.showMenu)                

        #connect actions
        self.actionTrackDelete.triggered.connect(self.trackDelete)
        self.actionTrackNew.triggered.connect(self.trackNew)
        self.actionTrackToggleNew.triggered.connect(self.trackToggleNew)
        self.actionTrackDetachedCopy.triggered.connect(self.trackDetachedCopy)
        receivermm = lambda : self.groupimport(what="mm")
        self.actionImport.triggered.connect(receivermm)
        self.actionQuit.triggered.connect(self.quit)

        self.actionGroupAllTracks.triggered.connect(self.groupAllTracks)
        self.actionGroupFavourite.triggered.connect(self.groupFavorite)
        self.actionGroupNew.triggered.connect(self.groupNew)

        receivercue = lambda : self.groupimport(what="cue")
        self.actionGroupImportCue.triggered.connect(receivercue)#self.groupImportCue)
        receiverdir = lambda : self.groupimport(what="dir")
        self.actionGroupImportDirectory.triggered.connect(receiverdir)#self.groupImportDir)
        self.actionGroupDelete.triggered.connect(self.groupDelete)
        self.actionGroupRename.triggered.connect(self.groupRename)

        self.menuTrack.aboutToShow.connect(self.updateMenuTrack)
        self.menuTrackMerge.aboutToShow.connect(self.updateMenuTrackMerge)
        self.menuTrackInGroups.aboutToShow.connect(self.updateMenuTrackInGroups)
        self.menuTrackAddToGroup.aboutToShow.connect(self.updateMenuTrackAddToGroup)
        self.menuTrackRelink.aboutToShow.connect(self.updateMenuTrackRelink)

        for act in [["0", lambda : self.trackRating(0), "`"],
                   ["1", lambda : self.trackRating(1), "1"],
                   ["2", lambda : self.trackRating(2), "2"],
                   ["3", lambda : self.trackRating(3), "3"],
                   ["4", lambda : self.trackRating(4), "4"],
                   ["5", lambda : self.trackRating(5), "5"],
                   ["6", lambda : self.trackRating(6), "6"],
                   ["7", lambda : self.trackRating(7), "7"],
                   ["8", lambda : self.trackRating(8), "8"],
                   ["9", lambda : self.trackRating(9), "9"],
                   ["10", lambda : self.trackRating(10), "0"]]:
                action = QtGui.QAction(self)
                action.setText(act[0])
                action.triggered.connect(act[1])
                if len(act) == 3:
                    action.setShortcut(QtGui.QKeySequence.fromString(act[2]))
                self.menuRating.addAction(action)
                self.trackstable.addAction(action)

        # this feeds all the default settings to the model and read the data from db
        self.filterSet()
        # FIXME the following causes that the model is refreshed three times

        self.tracks.sortingreallyenabled = False
        self.trackstable.sortByColumn(0,QtCore.Qt.AscendingOrder)
        self.trackstable.setSortingEnabled(True)
        self.tracks.sortingreallyenabled = True

        self.statusupdate()

    def showDBWarning(self):
        if CFG["regaudio"]["default"] == "True":
            QtGui.QMessageBox.warning(self, "Default database",
                    WARNMSG.format(CFGLOC, CFG["regaudio"]["db"])
                    , buttons=QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.Ok)

    def statusupdate(self):
        self.statusline.setText(self.stats.status())
        #sline = ""
        #if self.found >= 0:
        #    sline += "{} tracks found".format(self.found)
        #if self.new > 0 and self.new != self.found:
        #    if sline:
        #        sline += " "
        #    sline += "{} new".format(self.new)
        #if self.total > 0 and self.total != self.found:
        #    sline += " ({} total tracks)".format(self.total)
        #if sline != "":
        #    self.statusline.setText(sline)

    def pagesset(self):
        val = self.pagepageline.validator() # int validator
        val.setBottom(1)
        maxtr = int(self.pagemaxtracksline.text())
        if maxtr == 0:
            pages = 1
        elif self.stats.fltr == 0:
            pages = 1
        else:
            pages = int(self.stats.fltr / maxtr)
            if self.stats.fltr % maxtr:
                pages += 1
        self.pagemaxpagelbl.setText("/ {}".format(pages))
        val.setTop(pages)

        self.pagebuttons()
        self.statusupdate()

    def pagenext(self):
        page = int(self.pagepageline.text())
        self.pagepageline.setText(str(page+1))
        self.filterSet()
        self.pagebuttons()

    def pageprev(self):
        page = int(self.pagepageline.text())
        if page == 0:
            return
        self.pagepageline.setText(str(page-1))
        self.filterSet()
        self.pagebuttons()

    def pagebuttons(self):
        page = int(self.pagepageline.text())
        pagemin = self.pagepageline.validator().bottom()
        pagemax = self.pagepageline.validator().top()
        self.pagenextbtn.setEnabled(page != pagemax)
        self.pageprevbtn.setEnabled(page != pagemin)

    def updateMenuTrack(self):
        tt = self.trackstable
        rows = tt.model().getSelection(tt.selectedIndexes())
        gtmode = tt.model().dbmodel.gtmode
        # new track is always enabled
        # delete should be enabled only if there are tracks selected
        self.actionTrackDelete.setEnabled(len(rows) > 0)
        # merge should be enabled only if there is more than one track selected
        #self.menuTrackMerge.setEnabled(not gtmode and (len(rows) > 1))
        self.menuTrackMerge.setEnabled(len(rows) > 1)
        # add to group and groups list should be enabled if some tracks are selected
        self.menuTrackAddToGroup.setEnabled(len(rows) > 0)
        self.menuTrackInGroups.setEnabled(len(rows) > 0)
        self.menuTrackRelink.clear()
        self.menuTrackRelink.setEnabled(len(rows) == 1 and gtmode)
        self.actionTrackDetachedCopy.setEnabled(gtmode)

    def updateMenuTrackMerge(self):
        self.menuTrackMerge.clear()

        tt = self.trackstable
        dbmodel = tt.model().dbmodel
        rows = tt.model().getSelection(tt.selectedIndexes())

        for row in rows:
            tt = dbmodel.lst[row]
            if dbmodel.gtmode:
                tt = tt.track
            receiver = lambda row=row: self.trackMerge(rows, row)
            self.menuTrackMerge.addAction(tt.menucaption(), receiver)

    def updateMenuTrackInGroups(self):
        self.menuTrackInGroups.clear()

        tt = self.trackstable
        dbmodel = tt.model().dbmodel
        rows = tt.model().getSelection(tt.selectedIndexes())

        # first - make a dict of all the K: group id, V: number of tracks that share the same group
        gid_trscaps = {}
        for row in rows:
            tt = dbmodel.lst[row]
            if type(tt) == grouptrack.GroupTrack:
                tt = tt.track
            for gt in tt.grouptracks:
                gid = gt.group.idno # group id
                trs = gid_trscaps.setdefault(gid, [0,gt.group.name])[0] + 1 # tracks in that group
                gid_trscaps[gid][0] = trs

        # if no groups were found make only an informative menu entry
        if len(gid_trscaps) == 0:
            self.menuTrackInGroups.addAction("No group found")
            return

        for gid,cntcap in sorted(gid_trscaps.items(), key=lambda x:x[1][1]):
            receiver = lambda gid=gid: self.groupFocusGid(gid, True)
            #caption = gid_caps[gid]
            caption = cntcap[1]
            count = cntcap[0]
            if count > 1:
                caption = "({} tracks)  {}".format(count, caption)
            self.menuTrackInGroups.addAction(caption, receiver)

    def updateMenuTrackAddToGroup(self):
        # show maximum MAXGROUPS, show favorites and regular groups if there is at most MAXFAV_RATIO * MAXGROUPS favorites
        MAXGROUPS = 10
        MAXFAV_RATIO = 0.5

        self.menuTrackAddToGroup.clear()

        mg = self.groups
        mt = self.trackstable.model()

        gdb = self.groups.dbmodel
        tdb = self.trackstable.model().dbmodel

        #if mt.dbmodel.gtmode:
        #    self.menuTrackAddToGroup.addAction("Not available for group tracks")
        #    return

        tt = self.trackstable
        rows = tt.model().getSelection(tt.selectedIndexes())

        # each of the tracks can be in multiple groups
        gid_cnt = {}
        for row in rows:
            tt = tdb.lst[row]
            if mt.dbmodel.gtmode:
                tt = tt.track
            for gt in tt.grouptracks:
                gid = gt.group.idno # group id
                gid_cnt[gid] = gid_cnt.get(gid, 0) + 1 # tracks in that group

        nogrps = MAXGROUPS
        if gdb.favs >= MAXGROUPS * MAXFAV_RATIO and gdb.favs < MAXGROUPS:
            nogrps = MAXGROUPS

        showpicker = (nogrps < len(gdb.lst))

        for gg in gdb.lst:
            if gg is None:
                continue
            nogrps -= 1
            receiver = lambda gid=gg.idno: self.trackAddToGroup(rows, gid)
            caption = gg.menucaption()
            if gg.idno in gid_cnt:
                caption = "({}) ".format(gid_cnt[gg.idno]) + caption
            self.menuTrackAddToGroup.addAction(caption, receiver)
            if nogrps == 0:
                break
        if showpicker:
            receiver = lambda gid=gg.idno: self.trackAddToGroup(rows, None)
            self.menuTrackAddToGroup.addAction("Pick from all ...", receiver)

    def updateMenuTrackRelink(self):
        rows = self.tracks.getSelection(self.trackstable.selectedIndexes())

        assert(len(rows) == 1)

        self.menuTrackRelink.clear()

        for tid,cap in self.tracks.dbmodel.similar(rows[0]):
            receiver = lambda tid=tid:self.trackRelink(tid)
            self.menuTrackRelink.addAction(cap, receiver)
        
    def updateMenuGroup(self):
        pass

    def getlastdirectory(self):
        """return last visited directory for file dialogs"""
        ll = self.lastdirectory
        if not ll:
            return os.path.expanduser("~")
        while not os.path.exists(ll) and ll:
            ll = os.path.dirname(ll)
        if not ll:
            return os.path.expanduser("~")
        return ll

    def groupimport(self, what):
        import os.path

        if what == "mm":
            source = QtGui.QFileDialog.getOpenFileName(self,
                    caption='Select a MM file', directory=self.getlastdirectory(),
                    filter='Freemind files (*.mm);;All files (*.*)')
        elif what == "cue":
            source = QtGui.QFileDialog.getOpenFileNames(self,
                    caption='Select a Cue file', directory=self.getlastdirectory(),
                    filter='Cue files (*.cue);;All files (*.*)')
        elif what == "dir":
            source = QtGui.QFileDialog.getExistingDirectory(self,
                    caption='Select a directory to import', directory=self.getlastdirectory(),
                    options=QtGui.QFileDialog.ShowDirsOnly)

        if source is None or len(source) == 0:
            return

        if type(source) is str:
            self.lastdirectory = source
        else:
            self.lastdirectory = source[0]

        imp = ImportData(self.datamdl, source)
        what2, msg = imp.readdatapre(what, source)
        if what2:
            result = QtGui.QMessageBox.question(self, "Importing data", msg
                                                   , buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
                                                   , defaultButton=QtGui.QMessageBox.No)
            if result == QtGui.QMessageBox.Yes:
                what = what2

        imp.readdata(what, source)

        iw = ImportWindow(imp, parent=self)
        if not iw.exec_():
            return

        self.groups.beginResetModel()
        gid = imp.store(self.datamdl.getTracks().stats)
        self.groups.dbmodel.loadgroups()
        self.groups.endResetModel()
        self.groupFocusGid(gid)
        #self.groupbox.model().layoutChanged.emit()
        #self.trackstable.model().layoutChanged.emit()

    def groupFilterBool(self, ignored):
        self.groupFilter(focusfilter=True)

    def groupFilter(self, forcefilter=None, focusfilter=False):
        if self.nowgroupfiltering:
            return

        self.nowgroupfiltering = True
        try:
            if forcefilter != None:
                self.groupfilterline.setText(forcefilter)
                self.groupfilterbtn.setChecked(forcefilter != "")
            dofilter = self.groupfilterbtn.isChecked()
            self.groupfilterline.setVisible(dofilter)
            self.groupfilterline.setEnabled(dofilter)
            if not dofilter:
                self.groupfilterline.setText("")
            elif focusfilter:
                self.groupfilterline.setFocus(QtCore.Qt.OtherFocusReason)
            txt = self.groupfilterline.text()
            self.filtergroups.setFilterWildcard(txt)
        finally:
            self.nowgroupfiltering = False

    def groupSet(self):
        self.prevgroup = None
        self.filterSet()

    def groupNew(self):
        groupname, ok = QtGui.QInputDialog.getText(self, "New group", "New group name:")
        if not ok:
            return
        #newidx = self.groupbox.model().newGroup(groupname)
        newidx = self.groups.newGroup(groupname)
        if newidx == -1:
            QtGui.QMessageBox.warning(self, "New group",
                    "Couldn't create the group! Such group already exists!")
            return

        self.groupFocus(newidx, refresh=True)

    def groupDelete(self):
        #idx = self.groupbox.currentIndex()
        fidx = self.filtergroups.index(self.groupbox.currentIndex(), 0)
        idx = self.filtergroups.mapToSource(fidx).row()
        if idx == 0:
            QtGui.QMessageBox.warning(self, "A warning", "All Tracks is not a group", buttons=QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.NoButton)
            return

        #mm = self.groupbox.model()
        name = self.groups.getGroup(idx).name

        result = QtGui.QMessageBox.question(self, "Delete?", "Delete the group '" + name + "' ?"
                                                   , buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
                                                   , defaultButton=QtGui.QMessageBox.No)
        if result != QtGui.QMessageBox.Yes:
            return

        self.groups.removeRows(idx, 1)
        #self.groupFocus(0, refresh=True)

    def groupRename(self):
        #idx = self.groupbox.currentIndex()
        fidx = self.filtergroups.index(self.groupbox.currentIndex(), 0)
        idx = self.filtergroups.mapToSource(fidx)
        idx = idx.row()
        if idx == 0:
            QtGui.QMessageBox.warning(self, "A warning",
                    "All Tracks is not a group!",
                    buttons=QtGui.QMessageBox.Ok,
                    defaultButton=QtGui.QMessageBox.NoButton)
            return

        #mm = self.groupbox.model()
        #name = mm.getGroup(self.groupbox.currentIndex()).name
        name = self.groups.getGroup(idx).name

        groupname, ok = QtGui.QInputDialog.getText(self, "Rename group",
                "Change group name:" + " "*30, text=name)
        if not ok:
            return False

        if groupname == name:
            return

        # this will in turn change the index too (handled by feedback)
        #self.groups.setData(self.groups.index(idx, 0, QtCore.QModelIndex()), groupname, QtCore.Qt.EditRole)
        newidx = self.groups.renameGroup(idx, groupname)
        if newidx == None:
            QtGui.QMessageBox.warning(self, "Rename group",
                    "Couldn't rename the group! Such group already exists!")
            return
        self.groupFocus(newidx)

    def groupFavorite(self):
        #newidx = self.groupbox.model().favorite(self.groupbox.currentIndex())
        fidx = self.filtergroups.index(self.groupbox.currentIndex(), 0)
        idx = self.filtergroups.mapToSource(fidx).row()
        if idx == 0:
            QtGui.QMessageBox.warning(self, "A warning",
                    "All Tracks is not a group!",
                    buttons=QtGui.QMessageBox.Ok,
                    defaultButton=QtGui.QMessageBox.NoButton)
            return

        newidx = self.groups.favorite(idx)

        if newidx == -1:
            return
        self.groupFocus(newidx)

    def groupAllTracks(self):
        if self.prevgroup:
            self.groupFocus(self.prevgroup, refresh=True)
            self.prevgroup = None
        else:
            fidx = self.filtergroups.index(self.groupbox.currentIndex(), 0)
            idx = self.filtergroups.mapToSource(fidx).row()
            self.prevgroup = idx
            if self.prevgroup == 0:
                self.prevgroup = None

            self.groupFilter("")
            self.groupbox.blockSignals(True)
            self.groupbox.setCurrentIndex(0)
            self.groupbox.blockSignals(False)
            self.filterSet()

    def groupFocusGid(self, gid, refresh=False):
        self.groupFocus(self.groups.dbmodel.gidToIdx(gid), refresh)

    def groupFocus(self, idx, refresh=False):
        # this functions is called whener groups have changed, lets invalidate the model
        #self.filtergroups.invalidate()
        # the idx is points to the original model, its integer
        # if the idx cannot be remapped to the new reset the filter and try to focus it again
        idx = self.groups.index(idx, 0)
        idxmap = self.filtergroups.mapFromSource(idx)
        if not idxmap.isValid():
            self.groupFilter("") # reset the filter
            idxmap = self.filtergroups.mapFromSource(idx)
            if not idxmap.isValid():
                raise ValueError("groupFocus: invalid group row index {}".format(idx.row()))

        self.prevgroup = None
        self.groupbox.setCurrentIndex(idxmap.row())
        if refresh:
            self.filterSet()

    def trackAddToGroup(self, rows, gid):
        if gid == None:
            # show picker of all the groups
            db = self.groups.dbmodel
            lst = db.getcaptions()
            name, ok = QtGui.QInputDialog.getItem(self, "Pick a group",
                    "Pick the group to add the tracks into",
                    lst, current=0, editable=False)
            # the captions don't include the All tracks group so ..
            if not ok:
                return
            idx = lst.index(name) # slooow
            idx += 1
            gid = db.lst[idx].idno

        d, dc = self.trackstable.model().dbmodel.addtogroup(rows, gid)
        if not d:
            return

        result = QtGui.QMessageBox.question(self, "Add tracks to group", \
                "The tracks\n\t{}\nare already in that group?\n\nDo you wish to add them anyway?".format(dc), \
                buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, \
                defaultButton=QtGui.QMessageBox.No)
        if result != QtGui.QMessageBox.Yes:
            return
        self.trackstable.model().dbmodel.addtogroup(d, gid, force=True)
        self.filterSet()

    def trackDelete(self):
        tt = self.trackstable
        dbmodel = tt.model().dbmodel
        rows = tt.model().getSelection(tt.selectedIndexes())
        rowsranges = utils.re_rangebyone(sorted(rows), count=True)

        if tt.model().dbmodel.gtmode:
            result = QtGui.QMessageBox.question(self, "Delete grouptracks", \
                    "Delete just the selected group tracks (yes) or also the tracks (all)?", \
                    buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.YesAll , \
                    defaultButton=QtGui.QMessageBox.No)
        else:
            result = QtGui.QMessageBox.question(self, "Delete tracks", \
                    "PERMAMENTLY delete the selected tracks?", \
                    buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, \
                    defaultButton=QtGui.QMessageBox.No)
        if result != QtGui.QMessageBox.Yes and result != QtGui.QMessageBox.YesAll:
            return

        # delete tracks from bottom to keep the right indices
        for s, e in reversed(rowsranges):
            tt.model().removeRows(s,e, trackstoo=(result == QtGui.QMessageBox.YesAll))

    def trackNew(self):
        index = self.trackstable.selectionModel().currentIndex()
        model = self.trackstable.model()

        if not model.insertRow(index.row()+1, index.parent()):
            return

        #self.updateActions()

        #for column in range(model.columnCount(index.parent())):
        #    child = model.index(index.row()+1, column, index.parent())
        #    model.setData(child, "[No lst]", QtCore.Qt.EditRole)

    def trackRating(self, rr):
        tt = self.trackstable
        dbmodel = tt.model().dbmodel
        rows = tt.model().getSelection(tt.selectedIndexes())
        tt.model().rating(rows, rr)

    def trackToggleNew(self):
        tt = self.trackstable
        dbmodel = tt.model().dbmodel
        rows = tt.model().getSelection(tt.selectedIndexes())
        tt.model().toggleNew(rows)

    def trackMerge(self, rows, torow):
        index = self.trackstable.selectionModel().currentIndex()
        model = self.trackstable.model()

        model.mergetracks(rows, torow)

    def trackDetachedCopy(self):
        rows = self.tracks.getSelection(self.trackstable.selectedIndexes())
        assert(len(rows) == 1)
        self.tracks.detachedcopy(rows[0])

    def trackRelink(self, tid):
        """link selected grouptrack to the track given by the id"""
        rows = self.tracks.getSelection(self.trackstable.selectedIndexes())
        assert(len(rows) == 1)
        self.tracks.relink(rows[0], tid)

    def tracksContextMenu(self, point):
        point.setX(point.x() + self.trackstable.verticalHeader().width())
        point.setY(point.y() + self.trackstable.horizontalHeader().height())
        self.menuTrack.exec_(self.trackstable.mapToGlobal(point))

    def filterSetTo(self, rule=""):
        # work around action triggred
        if not type(rule) is str:
            rule = ""
        self.filterline.setText(rule)
        self.filterSet()

    def filterHistoryUpdateMenu(self):
        self.menuFilterHistory.clear()
        for rule in reversed(self.filterhistory):
            receiver = lambda rule=rule: self.filterSetTo(rule)
            self.menuFilterHistory.addAction(rule, receiver)

    def filterSet(self):#, *args, **kvargs):
        if self.nowfiltering:
            return

        # get values from controls
        try:
            self.nowfiltering = True
            rule = self.filterline.text().strip().lower()
            if rule and (not self.filterhistory or self.filterhistory[-1] != rule):
                if rule in self.filterhistory:
                    self.filterhistory.remove(rule)
                # limit the size of the history to 20 entries
                if len(self.filterhistory) >= 19:
                    self.filterhistory = self.filterhistory[-19:]
                self.filterhistory.append(rule)
                if not self.filterrecall.isEnabled():
                    self.filterrecall.setEnabled(True)
           
            grp = group.Group.namefromcaption(self.groupbox.currentText())
            maxrows = int(self.pagemaxtracksline.text())
            page = int(self.pagepageline.text())

            self.tracks.setfilter(rule=rule, group=grp, maxrows=maxrows, page=page)
            
            if self.tracks.rowCount():
                pass # TODO maybe remember the rule if the search yielded any results
        finally:
            self.pagebuttons()
            self.nowfiltering = False

    def modelfeedback(self, name, value):
        '''
        process feedback from the underlying model(s)

        see also Feedback class

        messages are passed in the form of the name and value
        the name is the common name of the control or command
        the value is the value to be set

        the feedback should not cause any updates so the nowfiltering is set to true
        (nowfiltering causes that filterSet doesn't work)
        '''
        setlock = False
        try:
            if not self.nowfiltering:
                self.nowfiltering = True
                setlock = True
            if name == "group": # by m/gm/setData
                self.groupFocus(int(value))
            elif name == "statusupdate":
                self.statusupdate()
            elif name == "pagesset":
                self.pagesset()
            #elif name == "total":
            #    self.total = int(value)
            #    self.statusupdate()
            elif name == "page": # by m/m/setfilter
                self.pagepageline.setText(value)
            #elif name == "rule":
            #    self.filterline.setText(value)
            #elif name == "found":
            #    self.found = int(value)
            #    self.pagesset()
            #elif name == "new":
            #    self.new = (int(value))
            #    self.statusupdate()
            elif name == "max": # by m/m/setfilter
                self.pagemaxtracksline.setText(value)
            elif name == "resize": # by u/tm/sort and setfilter
                self.trackstable.resizeColumnsToContents()
                self.trackstable.resizeRowsToContents()
            elif name == "sortbycol": # by m/m/setfilter
                cc = (int(value))
                sign = cc > 0
                cc = cc - 1 if sign else - 1 - cc
                try:
                    self.trackstable.horizontalHeader().blockSignals(True)
                    #self.trackstable.blockSignals(True)
                    self.trackstable.horizontalHeader().setSortIndicator(cc,
                            QtCore.Qt.AscendingOrder if sign else QtCore.Qt.DescendingOrder)
                finally:
                    #self.trackstable.blockSignals(False)
                    self.trackstable.horizontalHeader().blockSignals(False)
            else:
                print("Unknown feedback received", name, value)
        finally:
            if setlock:
                self.nowfiltering = False

    def quit(self):
        self.close()