updated zenity frontend, added cli frontend
[lilass.git] / gui.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
18 # This file abstracts GUI stuff away, so that the actual dsl.py does not have to deal with it
19 import sys
20
21 '''
22 This module implements two functions:
23
24 def error(message):
25     This function displays the error message to the user in some appropriate fassion
26
27 def setup(internalResolutions, externalResolutions):
28     Both arguments are lists of (width, height) tuples of resolutions. You can use dsl.res2user to obtain a user-readable representation of a resolution tuple.
29     The user should be asked about his display setup preferences.
30     The function returns None if the user cancelled, and an instance of dsl.ScreenSetup otherwise.
31 '''
32 import subprocess, collections
33
34 from question_frontend import QuestionFrontend
35 from screen import processOutputIt
36
37 # Qt frontend
38 class QtFrontend:
39     def __init__(self):
40         from PyQt4 import QtGui
41         self.app = QtGui.QApplication(sys.argv)
42         print("Qt loaded")
43     
44     def error(self, message):
45         from PyQt4 import QtGui
46         QtGui.QMessageBox.critical(None, 'Fatal error', message)
47     
48     def setup(self, situation):
49         from qt_dialogue import PositionSelection
50         return PositionSelection(situation).run()
51     
52     @staticmethod
53     def isAvailable():
54         try:
55             import PyQt4
56             return True
57         except ImportError:
58             return False
59
60
61 # Zenity frontend
62 class ZenityFrontend(QuestionFrontend):
63     def error(self, message):
64         '''Displays a fatal error to the user'''
65         subprocess.check_call(["zenity", "--error", "--text="+message])
66     def userChoose (self, title, choices, returns, fallback):
67         assert len(choices) == len(returns)
68         args = ["zenity", "--list", "--text="+title, "--column="]+choices
69         switch = dict (list(zip (choices,returns)))
70         try:
71             for line in processOutputIt(*args):
72                 return switch.get(line.strip(), fallback)
73         except Exception:
74             # on user cancel, the return code of zenity is nonzero
75             return fallback
76         # if the output was empty
77         return fallback
78     def isAvailable():
79         try:
80             processOutputIt("zenity", "--version")
81             return True
82         except FileNotFoundError:
83             return False
84         except PermissionError:
85             return False
86
87 # CLI frontend
88 class CLIFrontend(QuestionFrontend):
89     def error(self, message):
90         print(message, file=sys.stderr)
91
92     def userChoose (self, title, choices, returns, fallback):
93         while True:
94             # print question
95             print(title)
96             for i in range(len(choices)):
97                 print("%d. %s"%(i,choices[i]))
98             print("Enter 'c' to cancel.")
99             # handle input
100             answer = input("> ")
101             if answer == "c":
102                 return None
103             #else
104             try:
105                 answerint = int(answer)
106                 if answerint >= 0 and answerint < len(choices):
107                     return returns[answerint]
108             except ValueError:
109                 pass
110             # if we are here something invalid was entered
111             print("INVALID ANSWER: '%s'" % answer)
112
113     @staticmethod
114     def isAvailable():
115         return True
116
117 # list of available frontends
118 frontends = collections.OrderedDict()
119 frontends["qt"] = QtFrontend
120 frontends["zenity"] = ZenityFrontend
121 frontends["cli"] = CLIFrontend
122
123 # get a frontend
124 def getFrontend(name = None):
125     # by name
126     if name is not None:
127         if name in frontends:
128             if frontends[name].isAvailable():
129                 return frontends[name]() # call constructor
130             else:
131                 raise Exception("Frontend %s not available" % name)
132         # frontend not found
133         raise Exception("Frontend %s not found" % name)
134     # auto-detect
135     for frontend in frontends.values():
136         if frontend.isAvailable():
137             return frontend() # call constructor
138     raise Exception("No frontend is available - this should not happen")