363904d4a9f361fb47b302ae1d7fd426e6c93018
[stack/code/dboxswitch.git] / gui.py
1 # -*- coding: utf-8 -*-
2
3 """
4 Dboxswitch dropbox profile switcher
5
6 license: Modified BSD License
7
8 Copyright (c) 2012,  <stack@inventati.org>
9 All rights reserved.
10
11 Redistribution and use in source and binary forms, with or without
12 modification, are permitted provided that the following conditions are met:
13     * Redistributions of source code must retain the above copyright
14       notice, this list of conditions and the following disclaimer.
15     * Redistributions in binary form must reproduce the above copyright
16       notice, this list of conditions and the following disclaimer in the
17       documentation and/or other materials provided with the distribution.
18     * Neither the name of the <organization> nor the
19       names of its contributors may be used to endorse or promote products
20       derived from this software without specific prior written permission.
21
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
26 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 """
34 import sys
35 import os
36 from PyQt4 import QtGui, QtCore
37 from PyQt4.QtCore import SIGNAL
38 from qmenutooltip import QMenuToolTip
39
40 from apperror import AppError
41 from settings import appconf
42
43 class Gui(QtGui.QDialog):
44     def __init__(self, prManager):
45         """ Gui init, an QApplication is created and stored here, also a main window and a trayIcon
46         are created.
47         Default actions are builded and profile manager stored from argument (instance of ProfHandler """
48
49         self.app = QtGui.QApplication(sys.argv)
50     
51         #check if system tray is avaiable on the system
52         if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
53             QtGui.QMessageBox.critical(None, "Systray",
54                     "I couldn't detect any system tray on this system.")
55             sys.exit(1)
56         
57         QtGui.QApplication.setQuitOnLastWindowClosed(False)
58     
59         super(Gui, self).__init__()
60
61         self.profiles = []
62
63         self.createActions()
64         self.createTrayIcon()
65         self.createMainDialog()
66
67         self.trayIcon.show()
68  
69         self.profileManager = prManager
70
71     def main(self): 
72         """ Like Gtk application this main executes the app, thi is somewhat writed for
73         compatibility in porting the app """
74
75         sys.exit(self.app.exec_())
76
77     def closeEvent(self, event):
78         """ Handle application closing """
79
80         if self.trayIcon.isVisible():
81             self.hide()
82             event.ignore()
83
84     def createActions(self):
85         """ Create actions for the various components """
86
87         self.manageprofiles = QtGui.QAction("Manage &Profiles", self,
88                 triggered=self.show)
89         self.quitAction = QtGui.QAction("&Quit", self,
90                 triggered=QtGui.qApp.quit)
91
92         #profile manager component
93         self.addProfileAction = QtGui.QAction("  Add &Profile  ", self,
94                 triggered=self.addProfile)
95
96     def createMainDialog(self):    
97         """ Build the main dialog layout """ 
98         
99         self.setWindowTitle("Profile manager / Dboxswitch - dropbox profile switcher")
100         #add a default action to create a profile
101         self.addButton = QtGui.QPushButton(QtGui.QIcon(appconf.addpicon), 
102                     "Add Profile")
103         self.addButton.clicked.connect(self.addProfile)
104         self.addButton.setVisible(False)
105
106         self.la            = QtGui.QFormLayout()
107
108         self.la.addRow(self.addButton)
109         self.la.sizePolicy = QtGui.QSizePolicy.Minimum
110         self.la.setVerticalSpacing(15)
111         self.la.setHorizontalSpacing(15)
112         self.formGroupBox = QtGui.QGroupBox("Manage profiles:")
113         self.formGroupBox.setLayout(self.la)
114         mainLayout = QtGui.QVBoxLayout()
115         mainLayout.addWidget(self.formGroupBox)
116         self.setLayout(mainLayout)
117
118
119     def show(self):
120         """ Show the main dialog for dealing with profiles """
121
122         profiles = self.profileManager.getProfilesList()
123
124         for pr in profiles:
125             prname = self.profileManager.getBaseProfileName(pr)
126             delButton = QtGui.QToolButton()
127             label     = QtGui.QLabel(prname)
128             self.la.addRow(label, delButton)
129             delAction = QtGui.QAction(QtGui.QIcon(appconf.delpicon),"Delete", self,
130                 triggered=self.deleteProfileAction(prname, (label, delButton)))
131             delButton.setDefaultAction(delAction)
132             self.profiles.append((label, delButton))
133
134         #if profiles was empty
135         if len(profiles) == 0:
136             self.addButton.setVisible(True)
137         else:
138             self.addButton.setVisible(False)
139
140         super(Gui, self).show()
141
142     def hide(self):
143         """ Hides the main dialog """
144         
145         #Hides all the profiles in the Dialog
146         #User can deal with profiles like removing the dir manually
147
148         for ws in self.profiles:
149             for w in ws:
150                 w.setVisible(False)
151                 w.setParent(None)
152         super(Gui, self).hide()
153
154     def createTrayIcon(self):
155          """ Builds a new tray icon with a context menu and an action for the profile manager menu """
156
157          #context menu build, right click
158          self.trayIconMenu = QtGui.QMenu(self)
159          self.trayIconMenu.addAction(self.manageprofiles)
160          self.trayIconMenu.addSeparator()
161          self.trayIconMenu.addAction(self.quitAction)
162
163          #create the tray with an incon
164          self.trayIcon = QtGui.QSystemTrayIcon(QtGui.QIcon(appconf.icon), self.app)
165          #attach a context menu for the right click to the tray
166          self.trayIcon.setContextMenu(self.trayIconMenu)
167          #baloon on hover for the tray
168          self.trayIcon.setToolTip(appconf.appname+" "+appconf.appversion+"\nRight Click to manage profiles.")
169          #left click profiles show for the tray
170          self.trayIcon.activated.connect(self.showTrayProfiles)
171
172
173     def showTrayProfiles(self,reason):
174         """ Pops up a system tray profile Manager with a list of activable profiles and an 
175         action to add a new One """
176
177         if reason in (QtGui.QSystemTrayIcon.Trigger, QtGui.QSystemTrayIcon.DoubleClick):
178             self.menuProfiles = QMenuToolTip()
179             self.menuProfiles.setTitle("Profiles")
180             QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_DontShowIconsInMenus, False)
181
182             #Get profiles from the ProfHandler embedded in the gui
183             profiles = self.profileManager.getProfilesList()
184
185             for prpath in profiles:
186                 pr = self.profileManager.getBaseProfileName(prpath)
187                 receiver = self.activateProfileAction(prpath)
188                 menuItem_Profile = self.menuProfiles.addAction(pr)
189                 if self.profileManager.isCurrentProfile(prpath):
190                     menuItem_Profile.setIcon(QtGui.QIcon(appconf.cpicon))
191                  
192                 #Using lambda function to pass additional arguments to the function, in this case the path of the profile
193                 self.connect(menuItem_Profile, SIGNAL('triggered()'), receiver)
194                 #set menu item ToolTip
195                 menuItem_Profile.setToolTip("Activate profile: "+pr)
196
197                 self.menuProfiles.addAction(menuItem_Profile)
198
199             self.menuProfiles.addSeparator()
200             #action and menu item for adding a New Profile
201             self.menuProfiles.addAction(self.addProfileAction)
202
203             self.menuProfiles.activateWindow()
204             self.menuProfiles.popup(QtGui.QCursor.pos())
205
206     def activateProfileAction(self, pr):
207         """ Returns a callable to be passed as an action for the switching profile mechanism 
208         to self.connect(menuItem_Profile... 
209         It compiles a function with a profile name as argument and also handle the displaying of errors."""
210
211         def f():
212             try:
213                 self.profileManager.activateProfile(pr)
214             except AppError, e:
215                 self.showError(str(e))
216         return f
217
218     def deleteProfileAction(self, pr, prWidgets):
219         """ Returns a callable to be passed as an action for the profile manager mechanism 
220         to self.connect(menuItem_Profile... 
221         It compiles a function with a profile name as argument and also handle the displaying of errors."""
222
223         def f():
224             try:
225                 self.profileManager.delProfile(pr)
226                 for w in prWidgets:
227                     w.setParent(None)
228                     w.setVisible(False)
229             except AppError, e:
230                 self.showError(str(e))
231         return f
232
233
234     def addProfile(self):
235         """ Gui frontend to add a new Profile, it requests the user a profile name 
236         through a QInputDialog and creates a new profile with the help of the ProfHandler embedded in the Gui """
237
238         self.setWindowTitle("Add New Profile - Dboxswitch - dropbox profile switcher")
239         self.resize(300, 100)
240         text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 
241                             'Enter profile name:')
242         if ok:
243             try:
244                 self.profileManager.addProfile(unicode(text))
245             except AppError, e:
246                 self.showError(str(e))
247
248     def showError(self, err):
249         """ Display an error message through a QErrorMessage """
250
251         self.setWindowTitle("Error - Dboxswitch - dropbox profile switcher")
252         self.resize(200, 100)
253         err = QtGui.QErrorMessage.showMessage(QtGui.QErrorMessage.qtHandler(), "Error: "+err)