1# ----------------------------------------------------------------------
2# $Id: qrpreferences.py 2640 2014-08-12 02:04:01Z thomas-sturm $
3# ----------------------------------------------------------------------
4# (c) 2010 T. Sturm, C. Zengler, 2011-2014 T. Sturm
5# ----------------------------------------------------------------------
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10#    * Redistributions of source code must retain the relevant
11#      copyright notice, this list of conditions and the following
12#      disclaimer.
13#    * Redistributions in binary form must reproduce the above
14#      copyright notice, this list of conditions and the following
15#      disclaimer in the documentation and/or other materials provided
16#      with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29#
30
31import os
32
33from types import StringType
34
35from PySide.QtCore import Qt
36from PySide.QtCore import Signal
37from PySide.QtCore import QSettings
38
39from PySide.QtGui import QDialog
40from PySide.QtGui import QWidget
41from PySide.QtGui import QListWidget
42from PySide.QtGui import QListWidgetItem
43from PySide.QtGui import QListView
44from PySide.QtGui import QStackedWidget
45from PySide.QtGui import QHBoxLayout
46from PySide.QtGui import QFormLayout
47from PySide.QtGui import QPushButton
48from PySide.QtGui import QVBoxLayout
49from PySide.QtGui import QFontDialog
50from PySide.QtGui import QFont
51from PySide.QtGui import QFontInfo
52from PySide.QtGui import QGroupBox
53from PySide.QtGui import QLabel
54from PySide.QtGui import QCheckBox
55from PySide.QtGui import QLineEdit
56from PySide.QtGui import QFontComboBox
57from PySide.QtGui import QComboBox
58from PySide.QtGui import QFontDatabase
59from PySide.QtGui import QMessageBox
60
61from qrlogging import fontLogger
62from qrlogging import signalLogger
63from qrlogging import traceLogger
64
65from qrdefaults import QtReduceDefaults
66from qrdefaults import QtReduceIconSets
67
68
69class QtReducePreferencePane(QDialog):
70    # QtReducePreferencePane are the dialog windows for setting preferences.
71    # Instances are created via menu or keyboard shortcut in QtReduceMainWindow.
72
73    def __init__(self, parent=None):
74        super(QtReducePreferencePane, self).__init__(parent)
75
76        self.__createContents()
77
78        self.toolBar = QtReducePreferencesToolBar(self)
79        self.worksheet = QtReducePreferencesWorksheet(self)
80        self.computation = QtReducePreferencesComputation(self)
81
82        self.pagesWidget = QStackedWidget()
83        self.pagesWidget.addWidget(self.toolBar)
84        self.pagesWidget.addWidget(self.worksheet)
85        self.pagesWidget.addWidget(self.computation)
86
87        self.pagesWidget.setCurrentIndex(
88            self.contentsWidget.row(self.contentsWidget.currentItem()))
89
90        closeButton = QPushButton(self.tr("Close"))
91        closeButton.clicked.connect(self.close)
92
93        horizontalLayout = QHBoxLayout()
94        horizontalLayout.addWidget(self.contentsWidget)
95        horizontalLayout.addWidget(self.pagesWidget)
96
97        buttonsLayout = QHBoxLayout()
98        buttonsLayout.addStretch(1)
99        buttonsLayout.addWidget(closeButton)
100
101        mainLayout = QVBoxLayout()
102        mainLayout.addLayout(horizontalLayout)
103        mainLayout.addLayout(buttonsLayout)
104
105        self.setLayout(mainLayout)
106
107        self.setWindowTitle(self.tr("QReduce Preferences"))
108
109    def changePage(self,current,previous):
110        if not current:
111            current = previous
112        QSettings().setValue("preferences/currentitem",current.text())
113        self.pagesWidget.setCurrentIndex(self.contentsWidget.row(current))
114
115    def __createContents(self):
116        self.contentsWidget = QListWidget()
117        self.contentsWidget.setViewMode(QListView.ListMode)
118        self.contentsWidget.setMovement(QListView.Static)
119
120        toolBar = QListWidgetItem(self.contentsWidget)
121        toolBar.setText(self.tr("Toolbar"))
122        toolBar.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
123
124        worksheet = QListWidgetItem(self.contentsWidget)
125        worksheet.setText(self.tr("Worksheet"))
126        worksheet.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
127
128        computation = QListWidgetItem(self.contentsWidget)
129        computation.setText(self.tr("Computation"))
130        computation.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
131
132        currentItem = QSettings().value("preferences/currentitem",
133                                        self.tr(QtReduceDefaults.CURRENTITEM))
134        if currentItem == self.tr("Toolbar"):
135            self.contentsWidget.setCurrentItem(toolBar)
136        elif currentItem == self.tr("Worksheet"):
137            self.contentsWidget.setCurrentItem(worksheet)
138        elif currentItem == self.tr("Computation"):
139            self.contentsWidget.setCurrentItem(computation)
140
141        self.contentsWidget.currentItemChanged.connect(self.changePage)
142
143
144class QtReduceComboBox(QComboBox):
145    def __init__(self):
146        super(QtReduceComboBox,self).__init__()
147        self.setFocusPolicy(Qt.NoFocus)
148        self.setEditable(False)
149
150
151class QtReduceIconSizeComboBox(QtReduceComboBox):
152    currentIconSizeChanged = Signal(StringType)
153
154    def __init__(self,parent=None):
155        super(QtReduceIconSizeComboBox,self).__init__()
156        self.currentIndexChanged.connect(self.currentIndexChangedHandler)
157
158    def currentIndexChangedHandler(self,index):
159        return self.currentIconSizeChanged.emit(self.currentText())
160
161
162class QtReducePreferencesToolBar(QWidget):
163    def __init__(self,parent=None):
164        super(QtReducePreferencesToolBar,self).__init__(parent)
165
166        settings = QSettings()
167
168        toolBarGroup = QGroupBox(self.tr("Toolbar"))
169
170        self.iconSetCombo = QtReduceComboBox()
171        iDbKeys = QtReduceIconSets().db.keys()
172        iDbKeys.sort()
173        self.iconSetCombo.addItems(iDbKeys)
174
175        settings.setValue("toolbar/iconset","Oxygen") # Hack
176
177        traceLogger.debug("toolbar/iconset=%s" % settings.value("toolbar/iconset"))
178
179        self.iconSetCombo.setCurrentIndex(
180            self.iconSetCombo.findText(
181                settings.value("toolbar/iconset",QtReduceDefaults.ICONSET)))
182
183        self.iconSizeCombo = QtReduceIconSizeComboBox()
184        self.iconSizeCombo.addItems(["16","22","32"])
185        self.iconSizeCombo.setCurrentIndex(
186            self.iconSizeCombo.findText(
187                str(settings.value("toolbar/iconsize",
188                                   QtReduceDefaults.ICONSIZE))))
189
190        self.showCombo = QtReduceComboBox()
191        self.showCombo.addItems([self.tr("Symbol and Text"),
192                                 self.tr("Only Symbol"),
193                                 self.tr("Only Text")])
194        self.showCombo.setCurrentIndex(self.showCombo.findText(
195            settings.value("toolbar/buttonstyle",
196                           self.tr(QtReduceDefaults.BUTTONSTYLE))))
197
198        toolBarLayout = QFormLayout()
199#        toolBarLayout.addRow(self.tr("Symbol Set"),self.iconSetCombo)
200        toolBarLayout.addRow(self.tr("Symbol Size"),self.iconSizeCombo)
201        toolBarLayout.addRow(self.tr("Show"),self.showCombo)
202
203        toolBarGroup.setLayout(toolBarLayout)
204
205        mainLayout = QVBoxLayout()
206        mainLayout.addWidget(toolBarGroup)
207
208        self.setLayout(mainLayout)
209
210
211class QtReduceFontComboBox(QtReduceComboBox):
212    currentFontChanged = Signal(QFont)
213
214    def __init__(self,parent=None):
215        super(QtReduceFontComboBox,self).__init__()
216        fdb = QFontDatabase()
217        l = []
218        self.fontDict = {}
219        for fam in fdb.families(QFontDatabase.Latin):
220            for sty in fdb.styles(fam):
221                if not fam in l and fdb.isFixedPitch(fam,sty) \
222                and not fdb.bold(fam,sty) and not fdb.italic(fam,sty) \
223                and self.__osxHack(fam):
224                    fontLogger.debug("family=%s, style=%s, isFixedPitch=%s" %
225                                     (fam, sty, fdb.isFixedPitch(fam,sty)))
226                    sizes = fdb.smoothSizes(fam,sty)
227                    if sizes:
228                        font = fdb.font(fam,sty,sizes[0])
229                        if not font.exactMatch():
230                            fontLogger.debug("no exactMatch for  %s %s %s" %
231                                             (fam,sty,sizes[0]))
232
233                        l += [fam]
234                        self.fontDict.update({str(fam):font})
235        l.sort
236        self.addItems(l)
237        self.currentIndexChanged.connect(self.currentIndexChangedHandler)
238
239    def __osxHack(self,fam):
240        if os.uname()[0] != "Darwin":
241            return True
242        if fam.find("Andale") != -1 \
243        or fam.find("Bitstream") != -1 \
244        or fam.find("Consolas") != -1 \
245        or fam.find("Courier") != -1 \
246        or fam.find("DejaVu") != -1 \
247        or fam.find("Lucida") != -1 \
248        or fam.find("Monaco") != -1:
249            return True
250        return False
251
252    def setCurrentFont(self,font):
253        info = QFontInfo(font)
254        self.setCurrentIndex(self.findText(info.family()))
255
256    def currentFont(self):
257        return self.fontDict[self.currentText()]
258
259    def currentIndexChangedHandler(self,index):
260        return self.currentFontChanged.emit(self.currentFont())
261
262
263class QtReduceFontSizeComboBox(QtReduceComboBox):
264    currentFontSizeChanged = Signal(StringType)
265
266    def __init__(self,parent=None):
267        super(QtReduceFontSizeComboBox,self).__init__()
268        self.currentIndexChanged.connect(self.currentIndexChangedHandler)
269
270    def currentFontSize(self):
271        return self.findText(currentSize)
272
273    def currentIndexChangedHandler(self,index):
274        return self.currentFontSizeChanged.emit(self.currentText())
275
276
277class QtReduceFontSizeComboBoxFs(QtReduceComboBox):
278    currentFontSizeChangedFs = Signal(StringType)
279
280    def __init__(self,parent=None):
281        super(QtReduceFontSizeComboBoxFs,self).__init__()
282        self.currentIndexChanged.connect(self.currentIndexChangedHandler)
283
284    def currentFontSize(self):
285        return self.findText(currentSize)
286
287    def currentIndexChangedHandler(self,index):
288        return self.currentFontSizeChangedFs.emit(self.currentText())
289
290
291class QtReducePreferencesWorksheet(QWidget):
292    def __init__(self,parent=None):
293        super(QtReducePreferencesWorksheet,self).__init__(parent)
294
295        fontGroup = QGroupBox(self.tr("Fonts"))
296
297        self.fontCombo = QtReduceFontComboBox(self)
298        self.setFocusPolicy(Qt.NoFocus)
299        self.fontCombo.setEditable(False)
300        self.fontCombo.setCurrentFont(self.parent().parent().controller.view.font())
301
302        self.sizeCombo = QtReduceFontSizeComboBox()
303        self.sizeComboFs = QtReduceFontSizeComboBoxFs()
304        self.findSizes(self.fontCombo.currentFont())
305        self.fontCombo.currentFontChanged.connect(self.findSizes)
306
307        fontLayout = QFormLayout()
308        fontLayout.addRow(self.tr("General Worksheet Font"),self.fontCombo)
309        fontLayout.addRow(self.tr("Font Size"),self.sizeCombo)
310        fontLayout.addRow(self.tr("Full Screen Font Size"),self.sizeComboFs)
311
312        fontGroup.setLayout(fontLayout)
313
314        mainLayout = QVBoxLayout()
315        mainLayout.addWidget(fontGroup)
316
317        self.setLayout(mainLayout)
318
319    def findSizes(self,font):
320        fontLogger.debug("font.key()=%s" % font.key())
321        fontDatabase = QFontDatabase()
322
323        self.sizeCombo.blockSignals(True)
324        self.sizeCombo.clear()
325
326        self.sizeComboFs.blockSignals(True)
327        self.sizeComboFs.clear()
328
329        styleStr = fontDatabase.styleString(font)
330        if fontDatabase.isSmoothlyScalable(font.family(),styleStr):
331            for size in QFontDatabase.standardSizes():
332                self.sizeCombo.addItem(str(size))
333                self.sizeComboFs.addItem(str(size))
334        else:
335            for size in fontDatabase.smoothSizes(font.family(),styleStr):
336                self.sizeCombo.addItem(str(size))
337                self.sizeComboFs.addItem(str(size))
338
339        self.sizeCombo.blockSignals(False)
340        self.sizeComboFs.blockSignals(False)
341
342        currentSize = unicode(QSettings().value("worksheet/fontsize",
343                                                QtReduceDefaults.FONTSIZE))
344        sizeIndex = self.sizeCombo.findText(currentSize)
345        self.sizeCombo.setCurrentIndex(sizeIndex)
346
347        currentSize = unicode(QSettings().value("worksheet/fontsizefs",
348                                                QtReduceDefaults.FONTSIZEFS))
349        sizeIndex = self.sizeCombo.findText(currentSize)
350        self.sizeComboFs.setCurrentIndex(sizeIndex)
351
352
353class QtReducePreferencesComputation(QWidget):
354    def __init__(self,parent=None):
355        super(QtReducePreferencesComputation,self).__init__(parent)
356
357        reduceGroup = QGroupBox("Reduce")
358
359        self.reduceBinary = QLineEdit()
360
361        # font = self.reduceBinary.font()
362        # font.setFamily(QSettings().value("worksheet/fontfamily",
363        #                                QtReduceDefaults.FONTFAMILY))
364        # self.reduceBinary.setFont(font)
365
366        self.reduceBinary.setText(QSettings().value("computation/reduce",
367                                                    QtReduceDefaults.REDUCE))
368
369        self.reduceBinary.editingFinished.connect(self.editingFinishedHandler)
370
371        reduceLayout = QFormLayout()
372        reduceLayout.addRow(self.tr("Reduce Binary"),self.reduceBinary)
373
374        reduceGroup.setLayout(reduceLayout)
375
376        mainLayout = QVBoxLayout()
377        mainLayout.addWidget(reduceGroup)
378
379        self.setLayout(mainLayout)
380
381    def editingFinishedHandler(self):
382        settings = QSettings()
383        old = settings.value("computation/reduce",QtReduceDefaults.REDUCE)
384        new = self.reduceBinary.text()
385        if old == new:
386            return
387        self.reduceBinary.blockSignals(True)
388        tit = "Change Binary?"
389        txt = self.tr("Do you really want to change this setting?")
390        itxt = self.tr("If yes, then the binary ")
391        itxt += '"' + new + '" '
392        itxt += self.tr("will be used at the next restart.")
393        mbox = QMessageBox(self)
394        mbox.setIcon(QMessageBox.Question)
395        mbox.setWindowModality(Qt.WindowModal)
396        mbox.setWindowTitle(tit)
397        mbox.setText(txt)
398        mbox.setInformativeText(itxt)
399        mbox.setStandardButtons(QMessageBox.Yes|QMessageBox.No)
400        button = mbox.exec_()
401        if button == QMessageBox.Yes:
402            settings.setValue("computation/reduce",new)
403        else:
404            self.reduceBinary.setText(old)
405        self.reduceBinary.blockSignals(False)
406