Ejemplo n.º 1
0
    def add_parameter(self, name, parameter_data, parameter_change):
        layout = self.frm_parameters.layout()
        qtitle = QLabel(parameter_data[3])
        qslider = QSlider(Qt.Horizontal)
        qmin = QLabel(str(parameter_data[0]))
        qmax = QLabel(str(parameter_data[1]))
        qvalue = QLabel(str(parameter_data[2]))
        qslider.setRange(parameter_data[0], parameter_data[1])
        qslider.setValue(parameter_data[2])
        qtitle.setAlignment(Qt.AlignHCenter)
        qvalue.setAlignment(Qt.AlignHCenter)
        pos = layout.rowCount()
        layout.addWidget(qtitle, pos, 0, 1, 3)
        layout.addWidget(qmin, pos + 1, 0, 1, 1)
        layout.addWidget(qslider, pos + 1, 1, 1, 1)
        layout.addWidget(qmax, pos + 1, 2, 1, 1)
        layout.addWidget(qvalue, pos + 2, 0, 1, 3)

        def value_changed(value):
            v = parameter_change(name, value)
            qvalue.setNum(v)

        qslider.valueChanged.connect(value_changed)
        qslider.setTracking(True)

        self.parameters[name] = qslider
Ejemplo n.º 2
0
class PlotLcm(QMainWindow):
    """ A class to plot an LCM type over time. """
    
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowTitle('LCM Plotter')

        self.createMenu()
        self.createMainFrame()
        
        # Each channel has data and axis associated with it. 
        # Dictionary key is the channel name and property.
        self.data = {}
        self.axes = {}
        self.lastPlot = None
        
        # Stop the program if CTRL-C is received
        self._stopEvent = threading.Event()
        signal.signal(signal.SIGINT, self.handleSigint)
        
        self._lcm = lcm.LCM()
        self.handlerThread = threading.Thread(target=self.pollLcm)
        self.handlerThread.setDaemon(True)
        self.handlerThread.start()
        
        self.connect(self, SIGNAL('redraw()'), self.on_draw) # Create redraw signal
        self.drawingThread = threading.Thread(target=self.drawLoop)
        self.drawingThread.setDaemon(True)
        self.drawingThread.start()
        
    def _cleanup(self):
        self._stopEvent.set()
        self.handlerThread.join()
        self.drawingThread.join()
        
    def handleSigint(self, *args):
        self._cleanup()
        QApplication.quit()
        
    def pollLcm(self):
        while not self._stopEvent.isSet():
            rc = select.select([self._lcm.fileno()], [], [self._lcm.fileno()], 0.05)
            if len(rc[0]) > 0 or len(rc[2]) > 0:
                self._lcm.handle()
            
    def drawLoop(self):
        while not self._stopEvent.isSet():
            self.emit(SIGNAL("redraw()"))
            time.sleep(0.5)
            
    def handleMessage(self, channel, msg):
        for (lcmChannel, lcmType, lcmProperty) in self.data.keys():
            if lcmChannel == channel:
                data = eval(lcmType + ".decode(msg)." + lcmProperty)
                self.data[(lcmChannel, lcmType, lcmProperty)].append(data)
        
    def save_plot(self):
        file_choices = "PNG (*.png)|*.png"
        
        path = unicode(QFileDialog.getSaveFileName(self, 
                        'Save file', '', 
                        file_choices))
        if path:
            self.canvas.print_figure(path, dpi=self.dpi)
            self.statusBar().showMessage('Saved to %s' % path, 2000)
    
    def on_about(self):
        msg = """ Plot an LCM message
        """
        QMessageBox.about(self, "About the demo", msg.strip())
    
    def on_draw(self):
        """ Redraws the figure
        """
        for (channel, lcmType, lcmProperty) in self.axes.keys():
            axis = self.axes[(channel, lcmType, lcmProperty)]
            axis.clear()
            axis.grid(self.gridCheckBox.isChecked())
            axis.plot(self.data[(channel, lcmType, lcmProperty)])
            axis.set_title(channel + ": " + lcmProperty)
            
        self.canvas.draw()
    
    def addPlot(self):
        channel = str(self.channelTextbox.text()).strip()
        lcmType = str(self.typeTextbox.text()).strip()
        lcmProperty = str(self.propertyTextbox.text()).strip()
        
        if not self.checkInputs(channel, lcmType, lcmProperty):
            return
        
        self.data[(channel, lcmType, lcmProperty)] = []
        n = len(self.data)
        i = 0
        self.fig.clear() # Clear the old plot first
        for key in self.data.keys():
            i = i + 1
            self.axes[key] = self.fig.add_subplot(n, 1, i)
        
        self.lastPlot = (channel, lcmType, lcmProperty)
        self._lcm.subscribe(channel, self.handleMessage)
        
    def clearPlots(self):
        for key in self.data.keys():
            self.data[key] = []
    
    def checkInputs(self, channel, lcmType, lcmProperty):
        # Error checking cause nobody is perfect...
        if channel == "":
            print "Warning: No channel given"
            return False
        
        try:
            __import__("marof_lcm." + lcmType)
        except ImportError:
            print "Warning: The LCM type is not in scope"
            return False
        else:
            try:
                eval("getattr(" + lcmType + ", lcmProperty)")
            except Exception:
                print "Warning: The LCM property for this type does not exist"
                return False
        
        # Clear the data and don't create a new axis if there is already data for this        
        if self.data.has_key((channel, lcmType, lcmProperty)):
            print "This data already exists:", channel
            self.data[(channel, lcmType, lcmProperty)] = []
            return False
        
        return True
    
    def createMainFrame(self):
        self.mainFrame = QWidget()
        
        self.dpi = 72
        self.fig = Figure((5.0, 2.5), dpi=self.dpi, tight_layout=True)
        self.canvas = FigureCanvasQTAgg(self.fig)
        self.canvas.setParent(self.mainFrame)
        self.canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        self.mpl_toolbar = NavigationToolbar2QTAgg(self.canvas, self.mainFrame)
        
        # Other GUI controls
        # 
        self.channelTextbox = QLineEdit()
        self.channelTextbox.setMinimumWidth(100)
        self.typeTextbox = QLineEdit()
        self.typeTextbox.setMinimumWidth(100)
        self.propertyTextbox = QLineEdit()
        self.propertyTextbox.setMinimumWidth(100)
        #self.connect(self.textbox, SIGNAL('editingFinished ()'), self.on_draw)
        
        self.addPlotButton = QPushButton("Add Plot")
        self.connect(self.addPlotButton, SIGNAL('clicked()'), self.addPlot)
        
        self.mergePlotButton = QPushButton("Reset Data")
        self.connect(self.mergePlotButton, SIGNAL('clicked()'), self.clearPlots)
        
        self.gridCheckBox = QCheckBox("Show Grid")
        self.gridCheckBox.setChecked(False)
        self.connect(self.gridCheckBox, SIGNAL('stateChanged(int)'), self.on_draw)
        
        slider_label = QLabel('Bar width (%):')
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(1, 100)
        self.slider.setValue(20)
        self.slider.setTracking(True)
        self.slider.setTickPosition(QSlider.TicksBothSides)
        self.connect(self.slider, SIGNAL('valueChanged(int)'), self.on_draw)
        
        #
        # Layout with box sizers
        # 
        hbox = QHBoxLayout()
        
        for w in [self.channelTextbox, self.typeTextbox, self.propertyTextbox, 
                  self.addPlotButton, self.mergePlotButton, self.gridCheckBox, 
                  slider_label, self.slider]:
            hbox.addWidget(w)
            hbox.setAlignment(w, Qt.AlignVCenter)
        
        vbox = QVBoxLayout()
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.mpl_toolbar)
        vbox.addLayout(hbox)
        
        self.mainFrame.setLayout(vbox)
        self.setCentralWidget(self.mainFrame)
        
    def createMenu(self):
        
        # File menu        
        fileMenu = self.menuBar().addMenu("File")
        
        saveMenuItem = self.create_action("&Save plot",
            shortcut="Ctrl+S", slot=self.save_plot, 
            tip="Save the plot")
        
        quitAction = self.create_action("&Quit", slot=self.close, 
            shortcut="Ctrl+Q", tip="Close the application")
        
        self.add_actions(fileMenu, 
            (saveMenuItem, None, quitAction))
        
        # Help menu
        helpMenu = self.menuBar().addMenu("Help")
        aboutAction = self.create_action("&About", 
            shortcut='F1', slot=self.on_about, 
            tip='About the demo')
        
        self.add_actions(helpMenu, (aboutAction,))

    def add_actions(self, target, actions):
        for action in actions:
            if action is None:
                target.addSeparator()
            else:
                target.addAction(action)

    def create_action(  self, text, slot=None, shortcut=None, 
                        icon=None, tip=None, checkable=False, 
                        signal="triggered()"):
        action = QAction(text, self)
        if icon is not None:
            action.setIcon(QIcon(":/%s.png" % icon))
        if shortcut is not None:
            action.setShortcut(shortcut)
        if tip is not None:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot is not None:
            self.connect(action, SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def closeEvent(self, event):
        self._cleanup() # stop the drawing thread before exiting
        event.accept()
Ejemplo n.º 3
0
class PreferencesDialogBase(QDialog):
    def __init__(self, parent, app):
        flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
        QDialog.__init__(self, parent, flags)
        self.app = app
        self._setupUi()
        
        self.connect(self.filterHardnessSlider, SIGNAL("valueChanged(int)"), self.filterHardnessLabel.setNum)
        self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
    
    def _setupScanTypeBox(self, labels):
        self.scanTypeHLayout = QHBoxLayout()
        self.scanTypeLabel = QLabel(self)
        self.scanTypeLabel.setText(tr("Scan Type:"))
        self.scanTypeLabel.setMinimumSize(QSize(100, 0))
        self.scanTypeLabel.setMaximumSize(QSize(100, 16777215))
        self.scanTypeHLayout.addWidget(self.scanTypeLabel)
        self.scanTypeComboBox = QComboBox(self)
        for label in labels:
            self.scanTypeComboBox.addItem(label)
        self.scanTypeHLayout.addWidget(self.scanTypeComboBox)
        self.widgetsVLayout.addLayout(self.scanTypeHLayout)
    
    def _setupFilterHardnessBox(self):
        self.filterHardnessHLayout = QHBoxLayout()
        self.filterHardnessLabel = QLabel(self)
        self.filterHardnessLabel.setText(tr("Filter Hardness:"))
        self.filterHardnessLabel.setMinimumSize(QSize(0, 0))
        self.filterHardnessHLayout.addWidget(self.filterHardnessLabel)
        self.filterHardnessVLayout = QVBoxLayout()
        self.filterHardnessVLayout.setSpacing(0)
        self.filterHardnessHLayoutSub1 = QHBoxLayout()
        self.filterHardnessHLayoutSub1.setSpacing(12)
        self.filterHardnessSlider = QSlider(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.filterHardnessSlider.sizePolicy().hasHeightForWidth())
        self.filterHardnessSlider.setSizePolicy(sizePolicy)
        self.filterHardnessSlider.setMinimum(1)
        self.filterHardnessSlider.setMaximum(100)
        self.filterHardnessSlider.setTracking(True)
        self.filterHardnessSlider.setOrientation(Qt.Horizontal)
        self.filterHardnessHLayoutSub1.addWidget(self.filterHardnessSlider)
        self.filterHardnessLabel = QLabel(self)
        self.filterHardnessLabel.setText("100")
        self.filterHardnessLabel.setMinimumSize(QSize(21, 0))
        self.filterHardnessHLayoutSub1.addWidget(self.filterHardnessLabel)
        self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub1)
        self.filterHardnessHLayoutSub2 = QHBoxLayout()
        self.filterHardnessHLayoutSub2.setContentsMargins(-1, 0, -1, -1)
        self.moreResultsLabel = QLabel(self)
        self.moreResultsLabel.setText(tr("More Results"))
        self.filterHardnessHLayoutSub2.addWidget(self.moreResultsLabel)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.filterHardnessHLayoutSub2.addItem(spacerItem)
        self.fewerResultsLabel = QLabel(self)
        self.fewerResultsLabel.setText(tr("Fewer Results"))
        self.filterHardnessHLayoutSub2.addWidget(self.fewerResultsLabel)
        self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub2)
        self.filterHardnessHLayout.addLayout(self.filterHardnessVLayout)
    
    def _setupBottomPart(self):
        # The bottom part of the pref panel is always the same in all editions.
        self.fontSizeLabel = QLabel(tr("Font size:"))
        self.fontSizeSpinBox = QSpinBox()
        self.fontSizeSpinBox.setMinimum(5)
        self.widgetsVLayout.addLayout(horizontalWrap([self.fontSizeLabel, self.fontSizeSpinBox, None]))
        self.languageLabel = QLabel(tr("Language:"), self)
        self.languageComboBox = QComboBox(self)
        for lang in SUPPORTED_LANGUAGES:
            self.languageComboBox.addItem(LANGNAMES[lang])
        self.widgetsVLayout.addLayout(horizontalWrap([self.languageLabel, self.languageComboBox, None]))
        self.copyMoveLabel = QLabel(self)
        self.copyMoveLabel.setText(tr("Copy and Move:"))
        self.widgetsVLayout.addWidget(self.copyMoveLabel)
        self.copyMoveDestinationComboBox = QComboBox(self)
        self.copyMoveDestinationComboBox.addItem(tr("Right in destination"))
        self.copyMoveDestinationComboBox.addItem(tr("Recreate relative path"))
        self.copyMoveDestinationComboBox.addItem(tr("Recreate absolute path"))
        self.widgetsVLayout.addWidget(self.copyMoveDestinationComboBox)
        self.customCommandLabel = QLabel(self)
        self.customCommandLabel.setText(tr("Custom Command (arguments: %d for dupe, %r for ref):"))
        self.widgetsVLayout.addWidget(self.customCommandLabel)
        self.customCommandEdit = QLineEdit(self)
        self.widgetsVLayout.addWidget(self.customCommandEdit)
    
    def _setupAddCheckbox(self, name, label, parent=None):
        if parent is None:
            parent = self
        cb = QCheckBox(parent)
        cb.setText(label)
        setattr(self, name, cb)
    
    def _setupPreferenceWidgets(self):
        # Edition-specific
        pass
    
    def _setupUi(self):
        self.setWindowTitle(tr("Preferences"))
        self.resize(304, 263)
        self.setSizeGripEnabled(False)
        self.setModal(True)
        self.mainVLayout = QVBoxLayout(self)
        self.widgetsVLayout = QVBoxLayout()
        self._setupPreferenceWidgets()
        self.mainVLayout.addLayout(self.widgetsVLayout)
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok|QDialogButtonBox.RestoreDefaults)
        self.mainVLayout.addWidget(self.buttonBox)
        if (not ISOSX) and (not ISLINUX):
            self.mainVLayout.removeWidget(self.ignoreHardlinkMatches)
            self.ignoreHardlinkMatches.setHidden(True)
    
    def _load(self, prefs, setchecked):
        # Edition-specific
        pass
    
    def _save(self, prefs, ischecked):
        # Edition-specific
        pass
    
    def load(self, prefs=None):
        if prefs is None:
            prefs = self.app.prefs
        self.filterHardnessSlider.setValue(prefs.filter_hardness)
        self.filterHardnessLabel.setNum(prefs.filter_hardness)
        setchecked = lambda cb, b: cb.setCheckState(Qt.Checked if b else Qt.Unchecked)
        setchecked(self.mixFileKindBox, prefs.mix_file_kind)
        setchecked(self.useRegexpBox, prefs.use_regexp)
        setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
        setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches)
        setchecked(self.debugModeBox, prefs.debug_mode)
        self.copyMoveDestinationComboBox.setCurrentIndex(prefs.destination_type)
        self.customCommandEdit.setText(prefs.custom_command)
        self.fontSizeSpinBox.setValue(prefs.tableFontSize)
        try:
            langindex = SUPPORTED_LANGUAGES.index(self.app.prefs.language)
        except ValueError:
            langindex = 0
        self.languageComboBox.setCurrentIndex(langindex)
        self._load(prefs, setchecked)
    
    def save(self):
        prefs = self.app.prefs
        prefs.filter_hardness = self.filterHardnessSlider.value()
        ischecked = lambda cb: cb.checkState() == Qt.Checked
        prefs.mix_file_kind = ischecked(self.mixFileKindBox)
        prefs.use_regexp = ischecked(self.useRegexpBox)
        prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
        prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches)
        prefs.debug_mode = ischecked(self.debugModeBox)
        prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex()
        prefs.custom_command = str(self.customCommandEdit.text())
        prefs.tableFontSize = self.fontSizeSpinBox.value()
        lang = SUPPORTED_LANGUAGES[self.languageComboBox.currentIndex()]
        oldlang = self.app.prefs.language
        if oldlang not in SUPPORTED_LANGUAGES:
            oldlang = 'en'
        if lang != oldlang:
            QMessageBox.information(self, "", tr("dupeGuru has to restart for language changes to take effect."))
        self.app.prefs.language = lang
        self._save(prefs, ischecked)
    
    #--- Events
    def buttonClicked(self, button):
        role = self.buttonBox.buttonRole(button)
        if role == QDialogButtonBox.ResetRole:
            self.resetToDefaults()
Ejemplo n.º 4
0
class MainWin(QMainWindow):
    def __init__(self, mx, spurs, fef, parent=None, plot_lib='mpl'):
        QMainWindow.__init__(self, parent)

        self.mx = mx
        self.spurset = spurs
        self.fef = fef
    
        if plot_lib == 'qwt':
            from chart.qwtchart import qwtchart as chart
        elif plot_lib == 'mpl':
            from chart.mplchart import mplchart as chart
        elif plot_lib == 'pg':
            from chart.pyqtgraphchart import pyqtgraphchart as chart
        else:
            raise NotImplementedError

        self.chart = chart(self.spurset, self.fef, self)
        self.create_menu_bar()
        self.create_main_frame()
        self.hookup()

    def hookup(self):
        # connect all the objects that are supposed to be watching each other
        # for changes and updates.
        self.mx.register(self.chart.draw_spurs)
        self.spurset.register(self.chart.draw_spurs)
        self.chart.picker_watch(self.fef)
        self.fef.register(self.chart.draw_fef)
        self.chart.draw_spurs(self.spurset)
        self.chart.draw_fef(self.fef)

    def IF_slide(self, i):
        """ callback method for the IF selection slider"""
        self.IFtextbox.setText(str(i))
        self.mx.IF = i

    def about(self):
        msg = '''
        A frequency-planing tool based on the method of spur distances.
        (A reference to the article will go here)
        For more info, view the README included with this program.

        Patrick Yeon, 2012'''
        QMessageBox.about(self, 'Spur Distance Chart', msg.strip())

    def mxtypecb(self, i):
        """ callback method for mixer configuration selection combobox"""
        self.mx.m, self.mx.n = [(-1, 1), (1, -1), (1,1)][i]
        # trigger anything watching the mixer
        self.mx.update_watchers()

    def create_main_frame(self):
        self.main_frame = QWidget()
        # Looking at the main frame as two columns. On the left there is the
        # chart and the IF control. In the right column we'll have range
        # settings, mixer settings, and maybe other stuff that comes up?

        self.IFtextbox = QLineEdit()
        self.IFtextbox.setMinimumWidth(6)
        self.IFtextbox.setText(str(self.spurset.mixer.IF))
        # TODO link up the textbox so that it can also be input

        self.IFslider = QSlider(Qt.Horizontal)
        # TODO I'd really like some form of slider that doesn't actually limit
        # the user. Also, if IFtextbox could modify the IF, that'd be nice.
        self.IFslider.setRange(int(self.mx.IF * 0.1), (self.mx.IF * 5))
        self.IFslider.setValue(self.mx.IF)
        self.IFslider.setTracking(True)
        self.IFslider.setTickPosition(QSlider.TicksAbove)
        step = max(1, int(0.01 * self.mx.IF))
        self.IFslider.setSingleStep(step)
        self.IFslider.setPageStep(step * 10)
        self.IFcid = self.connect(self.IFslider, SIGNAL('valueChanged(int)'),
                                  self.IF_slide)

        IFbar = QHBoxLayout()
        IFbar.addWidget(QLabel('IF'))
        IFbar.addWidget(self.IFtextbox)
        IFbar.addWidget(self.IFslider)
        IFbar.addStretch()

        leftcol = QVBoxLayout()
        leftcol.addWidget(self.chart.plot)
        leftcol.addLayout(IFbar)

        # left column done. Now the right-hand side
        rangebox = QVBoxLayout()
        for (prop, name, f) in [(spurset.RFmin, 'RFmin', self.spurset.RFmin),
                                (spurset.RFmax, 'RFmax', self.spurset.RFmax),
                                (spurset.dspan, 'dspan', self.spurset.dspan)]:
            rangebox.addLayout(Fbar(self.spurset, prop,
                                    name, f, 0, 10000))
        autocb = QCheckBox('Auto')
        # Disable it, won't be implemented for release
        # but leave it there, to nag me into doing it.
        autocb.setDisabled(True)
        rangebox.addWidget(autocb)

        # a line to report the front-end filter's limits
        fefstat = QHBoxLayout()
        fefstat.addWidget(QLabel('Filter Range: '))
        fefrange = QLabel('%d - %d' % (self.fef.start, self.fef.stop))
        # TODO not sure about the lambda here. Feels like if I give it time,
        # I'll sort out a sensible overall connection scheme.
        self.fef.register(lambda o: fefrange.setText('%d - %d' % 
                                                     (self.fef.start,
                                                      self.fef.stop)))
        fefstat.addWidget(fefrange)

        # mixer high/low-side injection picker
        mxbar = QHBoxLayout()
        mxbar.addWidget(QLabel('IF = '))
        mxtype = QComboBox()
        mxtype.addItem('LO - RF')
        mxtype.addItem('RF - LO')
        mxtype.addItem('RF + LO')
        # TODO this is ugly
        mxtype.setCurrentIndex([(-1, 1), (1, -1), (1,1)].index((self.mx.m,
                                                                self.mx.n)))
        self.mxtypecid = self.connect(mxtype,
                                      SIGNAL('currentIndexChanged(int)'),
                                      self.mxtypecb)
        mxbar.addWidget(mxtype)

        # alright, the actual column proper in the layout
        vbar = QVBoxLayout()
        vbar.addLayout(rangebox)
        vbar.addLayout(fefstat)
        vbar.addLayout(mxbar)
        legend = self.chart.legend()
        vbar.addWidget(legend)
        # need to let the legend stretch so that everything fits in it
        vbar.setStretchFactor(legend, 1)
        vbar.addStretch()

        hbox = QHBoxLayout()
        hbox.addLayout(leftcol)
        hbox.addLayout(vbar)
        # make sure the legend doesn't stretch so far horizontally that the
        # chart suffers considerable loss of space.
        hbox.setStretchFactor(leftcol, 5)
        hbox.setStretchFactor(vbar, 1)

        self.main_frame.setLayout(hbox)
        self.setCentralWidget(self.main_frame)

    def create_menu_bar(self):
        filemenu = self.menuBar().addMenu('&File')
        close = QAction('&Quit', self)
        close.setShortcut('Ctrl+W')
        self.connect(close, SIGNAL('triggered()'), self.close)
        filemenu.addAction(close)

        helpmenu = self.menuBar().addMenu('&Help')
        about  = QAction('&About', self)
        about.setShortcut('F1')
        self.connect(about, SIGNAL('triggered()'), self.about)
        helpmenu.addAction(about)
Ejemplo n.º 5
0
class Player(QWidget):
    def __init__(self,app):
        super(Player,self).__init__()
        self.mpd   = app.mpd
        self.ih    = app.imagehelper
        self.timer = None
        self.initGUI()
        self.initState()
        
    def initGUI(self):
        self.setWindowTitle(QApplication.applicationName())
        self.fmt = QFontMetrics(QFont())
        layout = QVBoxLayout()
        
        toplayout = QHBoxLayout()
        currentLayout = QGridLayout()
        currentLayout.setContentsMargins(0,20,0,20)
        currentLayout.setHorizontalSpacing(100)
        currentLayout.setVerticalSpacing(20)
        # current song information
        currentLayout.addWidget(QLabel("<b>Artist</b>"),1,0)
        self.artist = QLabel()
        currentLayout.addWidget(self.artist,1,1)
        currentLayout.addWidget(QLabel("<b>Title</b>"),2,0)
        self.title  = QLabel()
        currentLayout.addWidget(self.title,2,1)
        currentLayout.addWidget(QLabel("<b>Albumartist</b>"),3,0)
        self.albumartist = QLabel()
        currentLayout.addWidget(self.albumartist,3,1)
        currentLayout.addWidget(QLabel("<b>Album</b>"),4,0)
        self.album  = QLabel()
        currentLayout.addWidget(self.album,4,1)
        # playlist and song position
        self.playlistposition = QLabel()
        currentLayout.addWidget(self.playlistposition,5,0)
        poslayout = QHBoxLayout()
        poslayout.setSpacing(10)
        self.time = QLabel("00:00")
        poslayout.addWidget(self.time)
        self.position = QSlider(Qt.Horizontal)
        self.position.setTracking(False)
        self.position.setSingleStep(10)
        self.position.sliderReleased.connect( self.seek)
        self.position.sliderMoved.connect(
            lambda x: self.time.setText(self.mpd.timeString(x)))
        poslayout.addWidget(self.position)
        self.length = QLabel("00:00")
        poslayout.addWidget(self.length)
        currentLayout.addLayout(poslayout,5,1)
        toplayout.addLayout(currentLayout)
        layout.addLayout(toplayout)
        layout.addStretch(1)
        
        self.settingsWidget = QMenu()
        self.consumeBtn = self.settingsWidget.addAction("Consume")
        self.consumeBtn.setCheckable(True)
        self.consumeBtn.triggered.connect( lambda x: self.mpd.consume(int(x)))
        self.singleBtn  = self.settingsWidget.addAction("Single")
        self.singleBtn.setCheckable(True)
        self.singleBtn.triggered.connect( lambda x: self.mpd.single(int(x)))
        
        toolLayout = QHBoxLayout()
        self.settingsBtn = QToolButton()
        self.settingsBtn.setFixedSize(64,64)
        self.settingsBtn.setIcon( self.ih.settingsButton)
        self.settingsBtn.clicked.connect(self.showAdditionalControls)
        toolLayout.addWidget(self.settingsBtn)

        
        toolWidget = QStackedWidget()
        transpWidget = QWidget()
        transpLayout = QHBoxLayout()
        self.prevBtn = self.createButton(
            self.ih.prevButton, self.ih.prevButtonPressed)
        self.prevBtn.clicked.connect( lambda x: self.mpd.previous())
        transpLayout.addWidget(self.prevBtn)
        self.playBtn = self.createCheckButton(
            self.ih.playButton, self.ih.pauseButton)
        self.playBtn.clicked.connect( self.playPressed)
        transpLayout.addWidget(self.playBtn)
        self.stopBtn = self.createButton(
            self.ih.stopButton, self.ih.stopButtonPressed)
        self.stopBtn.clicked.connect( lambda x: self.mpd.stop())
        transpLayout.addWidget(self.stopBtn)
        self.nextBtn = self.createButton(
            self.ih.nextButton, self.ih.nextButtonPressed)
        self.nextBtn.clicked.connect( lambda x: self.mpd.next())
        transpLayout.addWidget(self.nextBtn)
        self.shuffleBtn = self.createCheckButton(
            self.ih.shuffleButton, self.ih.shuffleButtonPressed)
        self.shuffleBtn.toggled.connect(
            lambda x: self.mpd.random(1) if x else self.mpd.random(0))
        transpLayout.addWidget(self.shuffleBtn)
        self.repeatBtn = self.createCheckButton(
            self.ih.repeatButton, self.ih.repeatButtonPressed)
        self.repeatBtn.toggled.connect(
            lambda x: self.mpd.repeat(1) if x else self.mpd.repeat(0))
        transpLayout.addWidget(self.repeatBtn)
        transpLayout.addSpacing(64)
        transpWidget.setLayout(transpLayout)
        toolWidget.addWidget( transpWidget)
        
        self.volume = QSlider(Qt.Horizontal)
        self.volume.valueChanged.connect(self.mpd.setvol)
        toolWidget.addWidget(self.volume)
        
        toolLayout.addWidget(toolWidget)
        self.volumeBtn = QToolButton()
        self.volumeBtn.setFixedSize(64,64)
        self.volumeBtn.setCheckable(True)
        self.volumeBtn.setIcon( self.ih.volumeButton)
        self.volumeBtn.toggled.connect(
            lambda x: toolWidget.setCurrentIndex(x))
        toolLayout.addWidget(self.volumeBtn)
        layout.addLayout(toolLayout)
        self.setLayout(layout)

    def showAdditionalControls(self):
        pos = self.settingsBtn.pos()
        pos.setY( pos.y()-self.settingsWidget.sizeHint().height())
        self.settingsWidget.popup( self.mapToGlobal(pos))

    def createCheckButton(self, offIcon, onIcon):
        i = QIcon()
        i.addPixmap( offIcon.pixmap(64), QIcon.Normal, QIcon.Off)
        i.addPixmap(  onIcon.pixmap(64), QIcon.Normal, QIcon.On)
        b = QToolButton()
        b.setFixedSize(64,64)
        b.setCheckable(True)
        b.setIcon(i)
        b.setStyleSheet("* { background: transparent }")
        return b

    def createButton(self, normal, pressed):
        b = QToolButton()
        b.setFixedSize(64,64)
        b.setIcon(normal)
        b.setStyleSheet("* { background: transparent }")
        b.pressed.connect( lambda: b.setIcon(pressed))
        b.released.connect( lambda: b.setIcon(normal))
        return b
    
    def playPressed(self,state):
        if   self.state == 'stop': self.mpd.play()
        else: self.mpd.pause(int(not state))
        
    def initState(self):
        self.songid  = -1
        self.state = 'stop'
        self.artist.setText("Unknown Artist")
        self.title.setText("Unknown Title")
        self.albumartist.setText("Unknown Artist")
        self.album.setText("Unknown Album")
        self.position.setRange(0,0)
        self.position.setValue(0)
        self.position.setEnabled(False)
        self.length.setText("00:00")
        self.playlistposition.setText("0/0")
        self.setStopState()
        
    def setStopState(self):
        self.playBtn.setChecked(False)
        self.time.setText("00:00")
        self.position.setValue(0)
        self.volume.setEnabled(False)

    def updateStatus(self):
        status = self.mpd.status()
        self.repeatBtn.setChecked(int(status['repeat']))
        self.shuffleBtn.setChecked(int(status['random']))
        self.consumeBtn.setChecked(int(status['consume']))
        self.singleBtn.setChecked(int(status['single']))
        if not status.has_key('songid') and self.songid != -1:
            self.initState()
            return
        stateChanged = False
        state = status['state']
        if state != self.state:
            stateChanged = True
        self.state = state
        volume = int(status['volume'])
        if self.state == 'play' or self.state == 'pause':
            self.playBtn.setChecked(True if self.state == 'play' else False)
            songid = int(status['songid'])
            if songid != self.songid:
                self.songid = songid
                self.updateCurrentSong()
            playlistlength = int(status['playlistlength'])
            song = int(status.get('song',-1))
            self.playlistposition.setText("%d/%d" % (song+1,playlistlength))
            playlistlength = int(status['playlistlength'])
            elapsed = float(status['elapsed'])
            if not self.position.isSliderDown():
                timeString = self.mpd.timeString(round(elapsed))
                self.time.setFixedSize(
                    (len(timeString)+1)*self.fmt.averageCharWidth(),
                    self.fmt.height())
                self.time.setText(timeString)
                if self.position.maximum() > 0:
                    self.position.setSliderPosition(round(elapsed))
            if stateChanged:
                self.volume.setEnabled(True)
            if not self.volume.isSliderDown():
                self.volume.setValue(volume)
            #nextsong = int(status['nextsong'])
        else:
            if self.songid == -1: return
            self.setStopState()
            
    def updateCurrentSong(self):
        currentsong = self.mpd.unifySongInfo(self.mpd.currentsong())
        self.artist.setText(      currentsong['artist'])
        self.title.setText(       currentsong['title'])
        self.albumartist.setText( currentsong['albumartist'])
        self.album.setText(       currentsong['album'])
        self.length.setText(      currentsong['length'])
        self.position.setRange(0,currentsong['time'])
        self.position.setEnabled(True if self.position.maximum() > 0 else False)
        self.position.setValue(0)
        self.time.setText("00:00")
        self.volume.setEnabled(True)

    def seek(self):
        secs = self.position.sliderPosition()
        if self.songid == -1: return
        self.mpd.seekid(self.songid, secs)
       
    def hideEvent(self, ev):
        if self.timer:
            self.killTimer( self.timer)
            self.timer = None
        super(Player,self).hideEvent(ev)

    def showEvent(self,ev):
        if not self.timer:
            self.updateStatus()
            self.timer = self.startTimer(1000)
        super(Player,self).showEvent(ev)

    def timerEvent(self,ev):
        try:
            self.updateStatus()
        except ConnectionError, e:
            self.hide()
            QMaemo5InformationBox.information(
                self, "Connection Error: %s" % str(e),
                QMaemo5InformationBox.DefaultTimeout)
        except socket.error, e:
            QMaemo5InformationBox.information(
                self, "Connection Error: %s" % e[1],
                QMaemo5InformationBox.DefaultTimeout)
            self.hide()
Ejemplo n.º 6
0
class QtSlider(QtControl, ProxySlider):
    """ A Qt implementation of an Enaml ProxySlider.

    """
    #: A reference to the widget created by the proxy.
    widget = Typed(QSlider)

    #: Cyclic notification guard flags.
    _guard = Int(0)

    #--------------------------------------------------------------------------
    # Initialization API
    #--------------------------------------------------------------------------
    def create_widget(self):
        """ Create the underlying QSlider widget.

        """
        self.widget = QSlider(self.parent_widget())

    def init_widget(self):
        """ Initialize the underlying widget.

        """
        super(QtSlider, self).init_widget()
        d = self.declaration
        self.set_minimum(d.minimum)
        self.set_maximum(d.maximum)
        self.set_value(d.value)
        self.set_orientation(d.orientation)
        self.set_page_step(d.page_step)
        self.set_single_step(d.single_step)
        self.set_tick_interval(d.tick_interval)
        self.set_tick_position(d.tick_position)
        self.set_tracking(d.tracking)
        self.widget.valueChanged.connect(self.on_value_changed)

    #--------------------------------------------------------------------------
    # Signal Handlers
    #--------------------------------------------------------------------------
    def on_value_changed(self):
        """ Send the 'value_changed' action to the Enaml widget when the
        slider value has changed.

        """
        if not self._guard & VALUE_FLAG:
            self._guard |= VALUE_FLAG
            try:
                self.declaration.value = self.widget.value()
            finally:
                self._guard &= ~VALUE_FLAG

    #--------------------------------------------------------------------------
    # ProxySlider API
    #--------------------------------------------------------------------------
    def set_maximum(self, maximum):
        """ Set the maximum value of the underlying widget.

        """
        self.widget.setMaximum(maximum)

    def set_minimum(self, minimum):
        """ Set the minimum value of the underlying widget.

        """
        self.widget.setMinimum(minimum)

    def set_value(self, value):
        """ Set the value of the underlying widget.

        """
        if not self._guard & VALUE_FLAG:
            self._guard |= VALUE_FLAG
            try:
                self.widget.setValue(value)
            finally:
                self._guard &= ~VALUE_FLAG

    def set_page_step(self, page_step):
        """ Set the page step of the underlying widget.

        """
        self.widget.setPageStep(page_step)

    def set_single_step(self, single_step):
        """ Set the single step of the underlying widget.

        """
        self.widget.setSingleStep(single_step)

    def set_tick_interval(self, interval):
        """ Set the tick interval of the underlying widget.

        """
        self.widget.setTickInterval(interval)

    def set_tick_position(self, tick_position):
        """ Set the tick position of the underlying widget.

        """
        self.widget.setTickPosition(TICK_POSITION[tick_position])

    def set_orientation(self, orientation):
        """ Set the orientation of the underlying widget.

        """
        self.widget.setOrientation(ORIENTATION[orientation])

    def set_tracking(self, tracking):
        """ Set the tracking of the underlying widget.

        """
        self.widget.setTracking(tracking)
Ejemplo n.º 7
0
class MainWin(QMainWindow):
    def __init__(self, mx, spurs, fef, parent=None, plot_lib='mpl'):
        QMainWindow.__init__(self, parent)

        self.mx = mx
        self.spurset = spurs
        self.fef = fef

        if plot_lib == 'qwt':
            from chart.qwtchart import qwtchart as chart
        elif plot_lib == 'mpl':
            from chart.mplchart import mplchart as chart
        elif plot_lib == 'pg':
            from chart.pyqtgraphchart import pyqtgraphchart as chart
        else:
            raise NotImplementedError

        self.chart = chart(self.spurset, self.fef, self)
        self.create_menu_bar()
        self.create_main_frame()
        self.hookup()

    def hookup(self):
        # connect all the objects that are supposed to be watching each other
        # for changes and updates.
        self.mx.register(self.chart.draw_spurs)
        self.spurset.register(self.chart.draw_spurs)
        self.chart.picker_watch(self.fef)
        self.fef.register(self.chart.draw_fef)
        self.chart.draw_spurs(self.spurset)
        self.chart.draw_fef(self.fef)

    def IF_slide(self, i):
        """ callback method for the IF selection slider"""
        self.IFtextbox.setText(str(i))
        self.mx.IF = i

    def about(self):
        msg = '''
        A frequency-planing tool based on the method of spur distances.
        (A reference to the article will go here)
        For more info, view the README included with this program.

        Patrick Yeon, 2012'''
        QMessageBox.about(self, 'Spur Distance Chart', msg.strip())

    def mxtypecb(self, i):
        """ callback method for mixer configuration selection combobox"""
        self.mx.m, self.mx.n = [(-1, 1), (1, -1), (1, 1)][i]
        # trigger anything watching the mixer
        self.mx.update_watchers()

    def create_main_frame(self):
        self.main_frame = QWidget()
        # Looking at the main frame as two columns. On the left there is the
        # chart and the IF control. In the right column we'll have range
        # settings, mixer settings, and maybe other stuff that comes up?

        self.IFtextbox = QLineEdit()
        self.IFtextbox.setMinimumWidth(6)
        self.IFtextbox.setText(str(self.spurset.mixer.IF))
        # TODO link up the textbox so that it can also be input

        self.IFslider = QSlider(Qt.Horizontal)
        # TODO I'd really like some form of slider that doesn't actually limit
        # the user. Also, if IFtextbox could modify the IF, that'd be nice.
        self.IFslider.setRange(int(self.mx.IF * 0.1), (self.mx.IF * 5))
        self.IFslider.setValue(self.mx.IF)
        self.IFslider.setTracking(True)
        self.IFslider.setTickPosition(QSlider.TicksAbove)
        step = max(1, int(0.01 * self.mx.IF))
        self.IFslider.setSingleStep(step)
        self.IFslider.setPageStep(step * 10)
        self.IFcid = self.connect(self.IFslider, SIGNAL('valueChanged(int)'),
                                  self.IF_slide)

        IFbar = QHBoxLayout()
        IFbar.addWidget(QLabel('IF'))
        IFbar.addWidget(self.IFtextbox)
        IFbar.addWidget(self.IFslider)
        IFbar.addStretch()

        leftcol = QVBoxLayout()
        leftcol.addWidget(self.chart.plot)
        leftcol.addLayout(IFbar)

        # left column done. Now the right-hand side
        rangebox = QVBoxLayout()
        for (prop, name, f) in [(spurset.RFmin, 'RFmin', self.spurset.RFmin),
                                (spurset.RFmax, 'RFmax', self.spurset.RFmax),
                                (spurset.dspan, 'dspan', self.spurset.dspan)]:
            rangebox.addLayout(Fbar(self.spurset, prop, name, f, 0, 10000))
        autocb = QCheckBox('Auto')
        # Disable it, won't be implemented for release
        # but leave it there, to nag me into doing it.
        autocb.setDisabled(True)
        rangebox.addWidget(autocb)

        # a line to report the front-end filter's limits
        fefstat = QHBoxLayout()
        fefstat.addWidget(QLabel('Filter Range: '))
        fefrange = QLabel('%d - %d' % (self.fef.start, self.fef.stop))
        # TODO not sure about the lambda here. Feels like if I give it time,
        # I'll sort out a sensible overall connection scheme.
        self.fef.register(lambda o: fefrange.setText('%d - %d' % (
            self.fef.start, self.fef.stop)))
        fefstat.addWidget(fefrange)

        # mixer high/low-side injection picker
        mxbar = QHBoxLayout()
        mxbar.addWidget(QLabel('IF = '))
        mxtype = QComboBox()
        mxtype.addItem('LO - RF')
        mxtype.addItem('RF - LO')
        mxtype.addItem('RF + LO')
        # TODO this is ugly
        mxtype.setCurrentIndex([(-1, 1), (1, -1), (1, 1)].index(
            (self.mx.m, self.mx.n)))
        self.mxtypecid = self.connect(mxtype,
                                      SIGNAL('currentIndexChanged(int)'),
                                      self.mxtypecb)
        mxbar.addWidget(mxtype)

        # alright, the actual column proper in the layout
        vbar = QVBoxLayout()
        vbar.addLayout(rangebox)
        vbar.addLayout(fefstat)
        vbar.addLayout(mxbar)
        legend = self.chart.legend()
        vbar.addWidget(legend)
        # need to let the legend stretch so that everything fits in it
        vbar.setStretchFactor(legend, 1)
        vbar.addStretch()

        hbox = QHBoxLayout()
        hbox.addLayout(leftcol)
        hbox.addLayout(vbar)
        # make sure the legend doesn't stretch so far horizontally that the
        # chart suffers considerable loss of space.
        hbox.setStretchFactor(leftcol, 5)
        hbox.setStretchFactor(vbar, 1)

        self.main_frame.setLayout(hbox)
        self.setCentralWidget(self.main_frame)

    def create_menu_bar(self):
        filemenu = self.menuBar().addMenu('&File')
        close = QAction('&Quit', self)
        close.setShortcut('Ctrl+W')
        self.connect(close, SIGNAL('triggered()'), self.close)
        filemenu.addAction(close)

        helpmenu = self.menuBar().addMenu('&Help')
        about = QAction('&About', self)
        about.setShortcut('F1')
        self.connect(about, SIGNAL('triggered()'), self.about)
        helpmenu.addAction(about)
Ejemplo n.º 8
0
class PreferencesDialogBase(QDialog):
    def __init__(self, parent, app):
        flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
        QDialog.__init__(self, parent, flags)
        self.app = app
        self._setupUi()

        self.connect(self.filterHardnessSlider, SIGNAL("valueChanged(int)"),
                     self.filterHardnessLabel.setNum)
        self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'),
                     self.buttonClicked)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def _setupScanTypeBox(self, labels):
        self.scanTypeHLayout = QHBoxLayout()
        self.scanTypeLabel = QLabel(self)
        self.scanTypeLabel.setText(tr("Scan Type:"))
        self.scanTypeLabel.setMinimumSize(QSize(100, 0))
        self.scanTypeLabel.setMaximumSize(QSize(100, 16777215))
        self.scanTypeHLayout.addWidget(self.scanTypeLabel)
        self.scanTypeComboBox = QComboBox(self)
        for label in labels:
            self.scanTypeComboBox.addItem(label)
        self.scanTypeHLayout.addWidget(self.scanTypeComboBox)
        self.widgetsVLayout.addLayout(self.scanTypeHLayout)

    def _setupFilterHardnessBox(self):
        self.filterHardnessHLayout = QHBoxLayout()
        self.filterHardnessLabel = QLabel(self)
        self.filterHardnessLabel.setText(tr("Filter Hardness:"))
        self.filterHardnessLabel.setMinimumSize(QSize(0, 0))
        self.filterHardnessHLayout.addWidget(self.filterHardnessLabel)
        self.filterHardnessVLayout = QVBoxLayout()
        self.filterHardnessVLayout.setSpacing(0)
        self.filterHardnessHLayoutSub1 = QHBoxLayout()
        self.filterHardnessHLayoutSub1.setSpacing(12)
        self.filterHardnessSlider = QSlider(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.filterHardnessSlider.sizePolicy().hasHeightForWidth())
        self.filterHardnessSlider.setSizePolicy(sizePolicy)
        self.filterHardnessSlider.setMinimum(1)
        self.filterHardnessSlider.setMaximum(100)
        self.filterHardnessSlider.setTracking(True)
        self.filterHardnessSlider.setOrientation(Qt.Horizontal)
        self.filterHardnessHLayoutSub1.addWidget(self.filterHardnessSlider)
        self.filterHardnessLabel = QLabel(self)
        self.filterHardnessLabel.setText("100")
        self.filterHardnessLabel.setMinimumSize(QSize(21, 0))
        self.filterHardnessHLayoutSub1.addWidget(self.filterHardnessLabel)
        self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub1)
        self.filterHardnessHLayoutSub2 = QHBoxLayout()
        self.filterHardnessHLayoutSub2.setContentsMargins(-1, 0, -1, -1)
        self.moreResultsLabel = QLabel(self)
        self.moreResultsLabel.setText(tr("More Results"))
        self.filterHardnessHLayoutSub2.addWidget(self.moreResultsLabel)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        self.filterHardnessHLayoutSub2.addItem(spacerItem)
        self.fewerResultsLabel = QLabel(self)
        self.fewerResultsLabel.setText(tr("Fewer Results"))
        self.filterHardnessHLayoutSub2.addWidget(self.fewerResultsLabel)
        self.filterHardnessVLayout.addLayout(self.filterHardnessHLayoutSub2)
        self.filterHardnessHLayout.addLayout(self.filterHardnessVLayout)

    def _setupBottomPart(self):
        # The bottom part of the pref panel is always the same in all editions.
        self.fontSizeLabel = QLabel(tr("Font size:"))
        self.fontSizeSpinBox = QSpinBox()
        self.fontSizeSpinBox.setMinimum(5)
        self.widgetsVLayout.addLayout(
            horizontalWrap([self.fontSizeLabel, self.fontSizeSpinBox, None]))
        self.languageLabel = QLabel(tr("Language:"), self)
        self.languageComboBox = QComboBox(self)
        for lang in SUPPORTED_LANGUAGES:
            self.languageComboBox.addItem(LANGNAMES[lang])
        self.widgetsVLayout.addLayout(
            horizontalWrap([self.languageLabel, self.languageComboBox, None]))
        self.copyMoveLabel = QLabel(self)
        self.copyMoveLabel.setText(tr("Copy and Move:"))
        self.widgetsVLayout.addWidget(self.copyMoveLabel)
        self.copyMoveDestinationComboBox = QComboBox(self)
        self.copyMoveDestinationComboBox.addItem(tr("Right in destination"))
        self.copyMoveDestinationComboBox.addItem(tr("Recreate relative path"))
        self.copyMoveDestinationComboBox.addItem(tr("Recreate absolute path"))
        self.widgetsVLayout.addWidget(self.copyMoveDestinationComboBox)
        self.customCommandLabel = QLabel(self)
        self.customCommandLabel.setText(
            tr("Custom Command (arguments: %d for dupe, %r for ref):"))
        self.widgetsVLayout.addWidget(self.customCommandLabel)
        self.customCommandEdit = QLineEdit(self)
        self.widgetsVLayout.addWidget(self.customCommandEdit)

    def _setupAddCheckbox(self, name, label, parent=None):
        if parent is None:
            parent = self
        cb = QCheckBox(parent)
        cb.setText(label)
        setattr(self, name, cb)

    def _setupPreferenceWidgets(self):
        # Edition-specific
        pass

    def _setupUi(self):
        self.setWindowTitle(tr("Preferences"))
        self.resize(304, 263)
        self.setSizeGripEnabled(False)
        self.setModal(True)
        self.mainVLayout = QVBoxLayout(self)
        self.widgetsVLayout = QVBoxLayout()
        self._setupPreferenceWidgets()
        self.mainVLayout.addLayout(self.widgetsVLayout)
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Ok
                                          | QDialogButtonBox.RestoreDefaults)
        self.mainVLayout.addWidget(self.buttonBox)
        if (not ISOSX) and (not ISLINUX):
            self.mainVLayout.removeWidget(self.ignoreHardlinkMatches)
            self.ignoreHardlinkMatches.setHidden(True)

    def _load(self, prefs, setchecked):
        # Edition-specific
        pass

    def _save(self, prefs, ischecked):
        # Edition-specific
        pass

    def load(self, prefs=None):
        if prefs is None:
            prefs = self.app.prefs
        self.filterHardnessSlider.setValue(prefs.filter_hardness)
        self.filterHardnessLabel.setNum(prefs.filter_hardness)
        setchecked = lambda cb, b: cb.setCheckState(Qt.Checked
                                                    if b else Qt.Unchecked)
        setchecked(self.mixFileKindBox, prefs.mix_file_kind)
        setchecked(self.useRegexpBox, prefs.use_regexp)
        setchecked(self.removeEmptyFoldersBox, prefs.remove_empty_folders)
        setchecked(self.ignoreHardlinkMatches, prefs.ignore_hardlink_matches)
        setchecked(self.debugModeBox, prefs.debug_mode)
        self.copyMoveDestinationComboBox.setCurrentIndex(
            prefs.destination_type)
        self.customCommandEdit.setText(prefs.custom_command)
        self.fontSizeSpinBox.setValue(prefs.tableFontSize)
        try:
            langindex = SUPPORTED_LANGUAGES.index(self.app.prefs.language)
        except ValueError:
            langindex = 0
        self.languageComboBox.setCurrentIndex(langindex)
        self._load(prefs, setchecked)

    def save(self):
        prefs = self.app.prefs
        prefs.filter_hardness = self.filterHardnessSlider.value()
        ischecked = lambda cb: cb.checkState() == Qt.Checked
        prefs.mix_file_kind = ischecked(self.mixFileKindBox)
        prefs.use_regexp = ischecked(self.useRegexpBox)
        prefs.remove_empty_folders = ischecked(self.removeEmptyFoldersBox)
        prefs.ignore_hardlink_matches = ischecked(self.ignoreHardlinkMatches)
        prefs.debug_mode = ischecked(self.debugModeBox)
        prefs.destination_type = self.copyMoveDestinationComboBox.currentIndex(
        )
        prefs.custom_command = str(self.customCommandEdit.text())
        prefs.tableFontSize = self.fontSizeSpinBox.value()
        lang = SUPPORTED_LANGUAGES[self.languageComboBox.currentIndex()]
        oldlang = self.app.prefs.language
        if oldlang not in SUPPORTED_LANGUAGES:
            oldlang = 'en'
        if lang != oldlang:
            QMessageBox.information(
                self, "",
                tr("dupeGuru has to restart for language changes to take effect."
                   ))
        self.app.prefs.language = lang
        self._save(prefs, ischecked)

    #--- Events
    def buttonClicked(self, button):
        role = self.buttonBox.buttonRole(button)
        if role == QDialogButtonBox.ResetRole:
            self.resetToDefaults()