remove spurious spaces
[lilass.git] / qt_frontend.py
1 # DSL - easy Display Setup for Laptops
2 # Copyright (C) 2012-2015 Ralf Jung <post@ralfj.de>
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 import sys, os
18 from screen import RelativeScreenPosition, ScreenSetup
19
20 try:
21     # Be fine with PyQt4 not being installed
22     from PyQt5 import QtCore, QtWidgets, uic
23
24     class PositionSelection(QtWidgets.QDialog):
25         def __init__(self, situation):
26             # set up main window
27             super(PositionSelection, self).__init__()
28             self._situation = situation
29             uifile = os.path.join(os.path.dirname(__file__), 'qt_dialogue.ui')
30             uic.loadUi(uifile, self)
31             
32             # fill relative position box
33             for pos in RelativeScreenPosition:
34                 self.relPos.addItem(pos.text, pos)
35             
36             # keep resolutions in sync when in mirror mode
37             def syncIfMirror(source, target):
38                 def _slot(idx):
39                     if self.isMirror:
40                         target.setCurrentIndex(idx)
41                 source.currentIndexChanged.connect(_slot)
42             syncIfMirror(self.intRes, self.extRes)
43             syncIfMirror(self.extRes, self.intRes)
44
45             # if situation has a previousSetup, use its values as initial state
46             if situation.previousSetup:
47                 p = situation.previousSetup
48                 self.intEnabled.setChecked(p.intResolution is not None)
49                 self.extEnabled.setChecked(p.extResolution is not None)
50                 if p.relPosition:
51                     self.relPos.setCurrentIndex(p.relPosition.value - 1)
52                 if p.extIsPrimary:
53                     self.extPrimary.setChecked(True)
54                 else:
55                     self.intPrimary.setChecked(True)
56                 # Pre-select the previous resolution
57                 self._intDefaultRes = p.intResolution
58                 self._extDefaultRes = p.extResolution
59                 self._mirrorDefaultRes = p.intResolution if p.relPosition == RelativeScreenPosition.MIRROR else None # in case of a mirror, they would be the same anyway
60             else:
61                 self._intDefaultRes = situation.internalConnector.getPreferredResolution()
62                 self._extDefaultRes = situation.externalConnector.getPreferredResolution()
63                 self._mirrorDefaultRes = None
64
65             # connect the update function
66             self.intEnabled.toggled.connect(self.updateEnabledControls)
67             self.extEnabled.toggled.connect(self.updateEnabledControls)
68             self.relPos.currentIndexChanged.connect(self.updateEnabledControls)
69
70             # make sure we are in a correct state
71             self.updateEnabledControls()
72
73         def getRelativeScreenPosition(self):
74             idx = self.relPos.currentIndex()
75             return self.relPos.itemData(idx)
76         
77         def fillResolutionBox(self, box, resolutions, select = None):
78             # if the count did not change, update in-place (this avoids flicker)
79             if box.count() == len(resolutions):
80                 for idx, res in enumerate(resolutions):
81                     box.setItemText(idx, str(res))
82                     box.setItemData(idx, res)
83                     if res == select:
84                         box.setCurrentIndex(idx)
85             else:
86                 # first clear it
87                 while box.count() > 0:
88                     box.removeItem(0)
89                 # then fill it
90                 for res in resolutions:
91                     box.addItem(str(res), res)
92                     if res == select:
93                         box.setCurrentIndex(box.count() - 1) # select the most recently added one
94         
95         def updateEnabledControls(self):
96             intEnabled = self.intEnabled.isChecked()
97             extEnabled = self.extEnabled.isChecked()
98             bothEnabled = intEnabled and extEnabled
99             self.isMirror = bothEnabled and self.getRelativeScreenPosition() == RelativeScreenPosition.MIRROR # only if both are enabled, we can really mirror
100             # configure screen controls
101             self.intRes.setEnabled(intEnabled)
102             self.intPrimary.setEnabled(intEnabled and not self.isMirror)
103             self.extRes.setEnabled(extEnabled)
104             self.extPrimary.setEnabled(extEnabled and not self.isMirror)
105             if not intEnabled and extEnabled:
106                 self.extPrimary.setChecked(True)
107             elif not extEnabled and intEnabled:
108                 self.intPrimary.setChecked(True)
109             # which resolutions do we offer?
110             if self.isMirror:
111                 commonRes = self._situation.commonResolutions()
112                 self.fillResolutionBox(self.intRes, commonRes, select = self._mirrorDefaultRes)
113                 self.fillResolutionBox(self.extRes, commonRes, select = self._mirrorDefaultRes)
114                 self.intRes.setCurrentIndex(self.extRes.currentIndex())
115             else:
116                 self.fillResolutionBox(self.intRes, self._situation.internalConnector.getResolutionList(), select = self._intDefaultRes)
117                 self.fillResolutionBox(self.extRes, self._situation.externalConnector.getResolutionList(), select = self._extDefaultRes)
118             # configure position control
119             self.posGroup.setEnabled(bothEnabled)
120             self.posLabel1.setEnabled(bothEnabled)
121             self.posLabel2.setEnabled(bothEnabled)
122             self.relPos.setEnabled(bothEnabled)
123             # avoid having no screen
124             self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(intEnabled or extEnabled)
125         
126         def run(self):
127             self.exec_()
128             if not self.result(): return None
129             intRes = self.intRes.itemData(self.intRes.currentIndex()) if self.intEnabled.isChecked() else None
130             extRes = self.extRes.itemData(self.extRes.currentIndex()) if self.extEnabled.isChecked() else None
131             return ScreenSetup(intRes, extRes, self.getRelativeScreenPosition(), self.extPrimary.isChecked())
132 except ImportError:
133     pass
134
135 # Qt frontend
136 class QtFrontend:
137     def __init__(self):
138         from PyQt5 import QtWidgets
139         self.app = QtWidgets.QApplication(sys.argv)
140         print("Qt loaded")
141     
142     def error(self, message):
143         from PyQt5 import QtWidgets
144         QtWidgets.QMessageBox.critical(None, 'Fatal error', message)
145     
146     def setup(self, situation):
147         return PositionSelection(situation).run()
148     
149     @staticmethod
150     def isAvailable():
151         try:
152             import PyQt5
153             return True
154         except ImportError:
155             return False
156