Example #1
0
 def __init__(self, parent=None, settings_path=""):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME,
                              Resources.get_path('scudcloud.png'))
     self.settings = QSettings(self.settings_path + '/scudcloud.cfg',
                               QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id(
             "scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     self.stackedWidget = QtGui.QStackedWidget()
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     self.startURL = Resources.SIGNIN_URL
     if self.identifier is not None:
         self.startURL = self.domain()
     self.addWrapper(self.startURL)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(ScudCloud.minimized)
     self.installEventFilter(self)
     self.statusBar().showMessage('Loading Slack...')
Example #2
0
 def __init__(self, parent = None, settings_path = ""):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png'))
     self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     webView = Wrapper(self)
     webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
     self.stackedWidget = QtGui.QStackedWidget()
     self.stackedWidget.addWidget(webView)
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(ScudCloud.minimized)
     self.installEventFilter(self)
     if self.identifier is None:
         webView.load(QtCore.QUrl(Resources.SIGNIN_URL))
     else:
         webView.load(QtCore.QUrl(self.domain()))
     webView.show()
Example #3
0
 def __init__(self, parent = None, settings_path = ""):
     super(zcswebapp, self).__init__(parent)
     self.setWindowTitle('zcswebapp')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('zcswebapp.png'))
     self.settings = QSettings(self.settings_path + '/zcswebapp.cfg', QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id("zcswebapp.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     webView = Wrapper(self)
     webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
     self.stackedWidget = QtGui.QStackedWidget()
     self.stackedWidget.addWidget(webView)
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(zcswebapp.minimized)
     self.installEventFilter(self)
     if self.identifier is None:
         webView.load(QtCore.QUrl(Resources.SIGNIN_URL))
     else:
         webView.load(QtCore.QUrl(self.domain()))
     webView.show()
Example #4
0
 def __init__(self, parent = None, settings_path = ""):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png'))
     self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     self.stackedWidget = QtGui.QStackedWidget()
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     self.startURL = Resources.SIGNIN_URL
     if self.identifier is not None:
         self.startURL = self.domain()
     self.addWrapper(self.startURL)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(ScudCloud.minimized)
     self.installEventFilter(self)
     self.statusBar().showMessage('Loading Slack...')
Example #5
0
    def run(self):
        self._logger.info("Starting DanDrop v" + self._globals.version + "...")

        # Create and start the API server
        if self._config.enableApi:
            self._api_server = ApiServer(self, self._config.tcpListenIp,
                                         self._config.tcpListenPort)

        # Start the web server
        if self._config.enableWebServer:
            self._web_server = WebServer()

        # Check if the systray should be shown
        #if self._config.enableSystray:
        if True:
            self._logger.debug("Creating systray...")
            self._systray = Systray(self._globals)

# Create the file watcher
        self._fswatcher = FSWatcher(self._configOld)

        self._fswatchers = []
        for share in self._config.shares:
            print share.sync_folder

        # Start watching and syncing files
        self._fswatcher.watch()
Example #6
0
 def __init__(self, parent=None):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.notifier = Notifier(self.APP_NAME, get_resource_path('scudcloud.png'))
     self.settings = QSettings(expanduser("~")+"/.scudcloud", QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.leftPane = LeftPane(self)
     self.cookiesjar = PersistentCookieJar(self)
     webView = Wrapper(self)
     webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
     self.stackedWidget = QtGui.QStackedWidget()
     self.stackedWidget.addWidget(webView)
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     self.addMenu()
     self.tray = Systray(self)
     self.systray()
     self.installEventFilter(self)
     self.zoom()
     if self.identifier is None:
         webView.load(QtCore.QUrl(self.SIGNIN_URL))
     else:
         webView.load(QtCore.QUrl(self.domain()))
     webView.show()
Example #7
0
	def _setupUI(self):
		self.setWindowTitle(self.NAME)

		self._addMenu()
		self._addToolbar()

		self._tabs = QtGui.QTabWidget()
		self._tabs.setTabsClosable(True)
		self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused)
		self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose)

		self._editor = QtGui.QPlainTextEdit()
		self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2)
		self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor)))

		speakButton = QtGui.QPushButton(self._("&Send"))
		self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

		grid = QtGui.QGridLayout()
		grid.setRowStretch(0, 1)
		grid.addWidget(self._tabs, 0, 0, 1, -1)
		grid.addWidget(self._editor, 1, 0)
		grid.addWidget(speakButton, 1, 1)

		widget = QtGui.QWidget()
		widget.setLayout(grid)
		self.setCentralWidget(widget)

		tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
		self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused)
		widget.installEventFilter(tabWidgetFocusEventFilter)

		self.centralWidget().hide()

		size = self.getSetting("window", "size")

		if not size:
			size = QtCore.QSize(640, 480)

		self.resize(size)

		position = self.getSetting("window", "position")
		if not position:
			screen = QtGui.QDesktopWidget().screenGeometry()
			position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2)

		self.move(position)

		self._updateLayout()

		menu = QtGui.QMenu(self)
		menu.addAction(self._menus["file"]["connect"])
		menu.addAction(self._menus["file"]["disconnect"])
		menu.addSeparator()
		menu.addAction(self._menus["file"]["exit"])

		self._trayIcon = Systray(self._icon, self)
		self._trayIcon.setContextMenu(menu)
		self._trayIcon.setToolTip(self.DESCRIPTION)
Example #8
0
 def __init__(self, parent=None, settings_path=""):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME,
                              Resources.get_path('scudcloud.png'))
     self.settings = QSettings(self.settings_path + '/scudcloud.cfg',
                               QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id(
             "scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     self.stackedWidget = QtGui.QStackedWidget()
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     startURL = Resources.SIGNIN_URL
     if self.identifier is not None:
         startURL = self.domain()
     self.addWrapper(startURL)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(ScudCloud.minimized)
     self.installEventFilter(self)
     self.statusBar().showMessage('Loading Slack...')
     # Starting unread msgs counter
     self.setupTimer()
     # Watch for suspend/resume events
     if DBusQtMainLoop is not None:
         DBusQtMainLoop(set_as_default=True)
         dbus.SystemBus().add_signal_receiver(
             self.sleep, 'PrepareForSleep',
             'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
Example #9
0
    def __init__(self, *args):
        super().__init__(*args)

        import main
        from Settings import SettingsAccessor, DEFAULT_SETTINGS
        from xwaredpy import XwaredPy
        from etmpy import EtmPy
        from systray import Systray
        import mounts
        from Notify import Notifier
        from frontendpy import FrontendPy
        from Schedule import Scheduler

        logging.info("XWARE DESKTOP STARTS")
        self.setApplicationName("XwareDesktop")
        self.setApplicationVersion(__version__)

        os.chdir(os.path.dirname(os.path.abspath(__file__)))
        self.checkOneInstance()

        self.settings = SettingsAccessor(self,
                                         configFilePath=constants.CONFIG_FILE,
                                         defaultDict=DEFAULT_SETTINGS)

        # components
        self.xwaredpy = XwaredPy(self)
        self.etmpy = EtmPy(self)
        self.mountsFaker = mounts.MountsFaker()
        self.dbusNotify = Notifier(self)
        self.frontendpy = FrontendPy(self)
        self.scheduler = Scheduler(self)

        self.settings.applySettings.connect(self.slotCreateCloseMonitorWindow)

        self.mainWin = main.MainWindow(None)
        self.mainWin.show()
        self.sigMainWinLoaded.emit()

        self.systray = Systray(self)

        self.settings.applySettings.emit()

        if self.settings.get("internal", "previousversion") == "0.8":
            # upgraded or fresh installed
            from PyQt5.QtCore import QUrl
            from PyQt5.QtGui import QDesktopServices
            QDesktopServices.openUrl(
                QUrl("https://github.com/Xinkai/XwareDesktop/wiki/使用说明"))

        self.settings.set("internal", "previousversion", __version__)
Example #10
0
    def __init__(self, *args):
        super().__init__(*args)

        import main
        from Settings import SettingsAccessor, DEFAULT_SETTINGS
        from xwaredpy import XwaredPy
        from etmpy import EtmPy
        from systray import Systray
        import mounts
        from Notify import Notifier
        from frontendpy import FrontendPy
        from Schedule import Scheduler

        logging.info("XWARE DESKTOP STARTS")
        self.setApplicationName("XwareDesktop")
        self.setApplicationVersion(__version__)

        self.checkUsergroup()

        os.chdir(os.path.dirname(os.path.abspath(__file__)))
        self.checkOneInstance()

        self.settings = SettingsAccessor(self,
                                         configFilePath=constants.CONFIG_FILE,
                                         defaultDict=DEFAULT_SETTINGS)

        # components
        self.xwaredpy = XwaredPy(self)
        self.etmpy = EtmPy(self)
        self.mountsFaker = mounts.MountsFaker()
        self.dbusNotify = Notifier(self)
        self.frontendpy = FrontendPy(self)
        self.scheduler = Scheduler(self)

        self.settings.applySettings.connect(self.slotCreateCloseMonitorWindow)

        self.mainWin = main.MainWindow(None)
        self.mainWin.show()
        self.sigMainWinLoaded.emit()

        self.systray = Systray(self)

        self.settings.applySettings.emit()
Example #11
0
 def __init__(self, parent = None, settings_path = ""):
     super(ScudCloud, self).__init__(parent)
     self.setWindowTitle('ScudCloud')
     self.settings_path = settings_path
     self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png'))
     self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat)
     self.identifier = self.settings.value("Domain")
     if Unity is not None:
         self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
     else:
         self.launcher = DummyLauncher(self)
     self.webSettings()
     self.leftPane = LeftPane(self)
     self.stackedWidget = QtGui.QStackedWidget()
     centralWidget = QtGui.QWidget(self)
     layout = QtGui.QHBoxLayout()
     layout.setContentsMargins(0, 0, 0, 0)
     layout.setSpacing(0)
     layout.addWidget(self.leftPane)
     layout.addWidget(self.stackedWidget)
     centralWidget.setLayout(layout)
     self.setCentralWidget(centralWidget)
     startURL = Resources.SIGNIN_URL
     if self.identifier is not None:
         startURL = self.domain()
     self.addWrapper(startURL)
     self.addMenu()
     self.tray = Systray(self)
     self.systray(ScudCloud.minimized)
     self.installEventFilter(self)
     self.statusBar().showMessage('Loading Slack...')
     # Starting unread msgs counter
     self.setupTimer()
     # Watch for suspend/resume events
     if DBusQtMainLoop is not None:
         DBusQtMainLoop(set_as_default=True)
         dbus.SystemBus().add_signal_receiver(self.sleep, 'PrepareForSleep', 'org.freedesktop.login1.Manager', 'org.freedesktop.login1')
Example #12
0
class Snakefire(object):
	DOMAIN = "snakefire.org"
	NAME = "Snakefire"
	DESCRIPTION = "Snakefire: Campfire Linux Client"
	VERSION = "1.0.1"
	ICON = "snakefire.png"
	COLORS = {
		"time": "c0c0c0",
		"alert": "ff0000",
		"join": "cb81cb",
		"leave": "cb81cb",
		"topic": "808080",
		"upload": "000000",
		"message": "000000",
		"nick": "808080",
		"nickAlert": "ff0000",
		"nickSelf": "000080",
		"tabs": {
			"normal": None,
			"new": QtGui.QColor(0, 0, 255),
			"alert": QtGui.QColor(255, 0, 0)
		}
	}

	def __init__(self):
		self.DESCRIPTION = self._(self.DESCRIPTION)
		self._worker = None
		self._settings = {}
		self._canConnect = False
		self._cfDisconnected()
		self._qsettings = QtCore.QSettings()
		self._icon = QtGui.QIcon(":/icons/%s" % (self.ICON))
		self.setWindowIcon(self._icon)
		self.setAcceptDrops(True)
		self._setupUI()

		settings = self.getSettings("connection")

		self._canConnect = False
		if settings["subdomain"] and settings["user"] and settings["password"]:
			self._canConnect = True

		self._updateLayout()

		if settings["connect"]:
			self.connectNow()

	def _(self, string, module=None):
		return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))

	def showEvent(self, event):
		if self._trayIcon.isVisible():
			if self._trayIcon.isAlerting():
				self._trayIcon.stopAlert()
			return
		self._trayIcon.show()

	def dragEnterEvent(self, event):
		room = self.getCurrentRoom()
		canUpload = not self._rooms[room.id]["upload"] if room else False
		if canUpload and self._getDropFile(event):
			event.acceptProposedAction()

	def dropEvent(self, event):
		room = self.getCurrentRoom()
		path = self._getDropFile(event)
		if room and path:
			self._upload(room, path)

	def _getDropFile(self, event):
		files = []
		urls = event.mimeData().urls()
		if urls:
			for url in urls:
				path = url.path()
				if path and os.path.exists(path) and os.path.isfile(path):
					try:
						handle = open(str(path))
						handle.close()
						files.append(str(path))
					except Exception as e:
						pass
			if len(files) > 1:
			   files = []
		return files[0] if files else None

	def getSetting(self, group, setting):
		settings = self.getSettings(group, asString=False);
		return settings[setting] if setting in settings else None

	def setSetting(self, group, setting, value):
		self._qsettings.beginGroup(group);
		self._qsettings.setValue(setting, value)
		self._qsettings.endGroup();

	def getSettings(self, group, asString=True, reload=False):
		defaults = {
			"connection": {
				"subdomain": None,
				"user": None,
				"password": None,
				"ssl": False,
				"connect": False,
				"join": False,
				"rooms": []
			},
			"program": {
				"minimize": False
			}
		}

		if reload or not group in self._settings:
			settings = defaults[group] if group in defaults else {}

			self._qsettings.beginGroup(group);
			for setting in self._qsettings.childKeys():
				settings[str(setting)] = self._qsettings.value(setting).toPyObject()
			self._qsettings.endGroup();

			boolSettings = []
			if group == "connection":
				boolSettings += ["ssl", "connect", "join"]
			elif group == "program":
				boolSettings += ["minimize"]

			for boolSetting in boolSettings:
				try:
					settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False
				except:
					settings[boolSetting] = False

			if group == "connection" and settings["subdomain"] and settings["user"]:
				settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"])) 

			self._settings[group] = settings

		settings = self._settings[group]
		if asString:
			for setting in settings:
				if not isinstance(settings[setting], bool):
					settings[setting] = str(settings[setting]) if settings[setting] else ""

		return settings

	def setSettings(self, group, settings):
		self._settings[group] = settings;

		self._qsettings.beginGroup(group);
		for setting in self._settings[group]:
			if group != "connection" or setting != "password":
				self._qsettings.setValue(setting, settings[setting])
			elif settings["subdomain"] and settings["user"]:
				keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting]) 
		self._qsettings.endGroup();

		if group == "connection":
			self._canConnect = False
			if settings["subdomain"] and settings["user"] and settings["password"]:
				self._canConnect = True
			self._updateLayout()

	def exit(self):
		self._forceClose = True
		self.close()

	def changeEvent(self, event):
		if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized():
			self.hide()
			event.ignore()
		else:
			event.accept()

	def closeEvent(self, event):
		if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"):
			self.hide()
			event.ignore()
		else:
			if self.getSetting("connection", "join"):
				self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()]))

			self.disconnectNow()

			if hasattr(self, "_workers") and self._workers:
				for worker in self._workers:
					worker.terminate()
					worker.wait()

			if hasattr(self, "_worker") and self._worker:
				self._worker.terminate()
				self._worker.wait()

			self.setSetting("window", "size", self.size())
			self.setSetting("window", "position", self.pos())

			event.accept()

	def alerts(self):
		dialog = AlertsDialog(self)
		dialog.open()

	def options(self):
		dialog = OptionsDialog(self)
		dialog.open()

	def connectNow(self):
		if not self._canConnect:
			return

		self._connecting = True
		self.statusBar().showMessage(self._("Connecting with Campfire..."))
		self._updateLayout()

		settings = self.getSettings("connection")

		self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self)
		self._connectWorkerSignals(self._worker)
		self._worker.connect()

	def disconnectNow(self):
		self.statusBar().showMessage(self._("Disconnecting from Campfire..."))
		if self._worker and hasattr(self, "_rooms"):
			# Using keys() since the dict could be changed (by _cfRoomLeft())
			# while iterating on it
			for roomId in self._rooms.keys():
				if roomId in self._rooms and self._rooms[roomId]["room"]:
					self._worker.leave(self._rooms[roomId]["room"], False)
					
		self._cfDisconnected()
		self._updateLayout()

	def joinRoom(self, roomIndex=None):
		room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex())
		if not room:
			return

		self._toolBar["join"].setEnabled(False)
		self.statusBar().showMessage(self._("Joining room %s...") % room["name"])

		self._rooms[room["id"]] = {
			"room": None,
			"stream": None,
			"upload": None,
			"tab": None,
			"editor": None,
			"usersList": None,
			"topicLabel": None,
			"filesLabel": None,
			"uploadButton": None,
			"uploadLabel": None,
			"uploadWidget": None,
			"newMessages": 0
		}
		self._getWorker().join(room["id"])

	def speak(self):
		message = self._editor.document().toPlainText()
		room = self.getCurrentRoom()
		if not room or message.trimmed().isEmpty():
			return

		self.statusBar().showMessage(self._("Sending message to %s...") % room.name)
		self._getWorker().speak(room, unicode(message))
		self._editor.document().clear()

	def uploadFile(self):
		room = self.getCurrentRoom()
		if not room:
			return

		path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload"))
		if path:
			self._upload(room, str(path))

	def uploadCancel(self):
		room = self.getCurrentRoom()
		if not room:
			return

		if self._rooms[room.id]["upload"]:
			self._rooms[room.id]["upload"].stop().join()
			self._rooms[room.id]["upload"] = None

		self._rooms[room.id]["uploadWidget"].hide()

	def leaveRoom(self, roomId):
		if roomId in self._rooms:
			self.statusBar().showMessage(self._("Leaving room %s...") % self._rooms[roomId]["room"].name)
			self._getWorker().leave(self._rooms[roomId]["room"])

	def changeTopic(self):
		room = self.getCurrentRoom()
		if not room:
			return
		topic, ok = QtGui.QInputDialog.getText(self,
			self._("Change topic"),
			self._("Enter new topic for room %s") % room.name,
			QtGui.QLineEdit.Normal,
			room.topic
		)
		if ok:
			self.statusBar().showMessage(self._("Changing topic for room %s...") % room.name)
			self._getWorker().changeTopic(room, topic)

	def updateRoomUsers(self, roomId = None):
		if not roomId:
			room = self.getCurrentRoom()
			if room:
				roomId = room.id
		if roomId in self._rooms:
			self.statusBar().showMessage(self._("Getting users in %s...") % self._rooms[roomId]["room"].name)
			self._getWorker().users(self._rooms[roomId]["room"])

	def updateRoomUploads(self, roomId = None):
		if not roomId:
			room = self.getCurrentRoom()
			if room:
				roomId = room.id
		if roomId in self._rooms:
			self.statusBar().showMessage(self._("Getting uploads in %s...") % self._rooms[roomId]["room"].name)
			self._getWorker().uploads(self._rooms[roomId]["room"])

	def getCurrentRoom(self):
		index = self._tabs.currentIndex()
		for roomId in self._rooms.keys():
			if roomId in self._rooms and self._rooms[roomId]["tab"] == index:
				return self._rooms[roomId]["room"]

	def _cfStreamMessage(self, room, message, live=True, updateRoom=True):
		if (
			not message.user or 
			(live and message.is_text() and message.is_by_current_user()) or
			not room.id in self._rooms
		):
			return

		user = message.user.name
		notify = True
		alert = False

		if message.is_text() and not message.is_by_current_user():
			alert = self._matchesAlert(message.body)

		html = None
		if message.is_joining():
			html = "<font color=\"#%s\">" % self.COLORS["join"]
			html += "--&gt; %s joined %s" % (user, room.name)
			html += "</font>"
		elif message.is_leaving():
			html = "<font color=\"#%s\">" % self.COLORS["leave"]
			html += "&lt;-- %s has left %s" % (user, room.name)
			html += "</font>"
		elif message.is_text():
			body = self._plainTextToHTML(message.tweet["tweet"] if message.is_tweet() else message.body)
			if message.is_tweet():
				body = "<a href=\"%s\">%s</a> <a href=\"%s\">tweeted</a>: %s" % (
					"http://twitter.com/%s" % message.tweet["user"],
					message.tweet["user"], 
					message.tweet["url"],
					body
				)
			elif message.is_paste():
				body = "<br /><hr /><code>%s</code><hr />" % body
			else:
				body = self._autoLink(body)

			created = QtCore.QDateTime(
				message.created_at.year,
				message.created_at.month,
				message.created_at.day,
				message.created_at.hour,
				message.created_at.minute,
				message.created_at.second
			)
			created.setTimeSpec(QtCore.Qt.UTC)

			createdFormat = "h:mm ap"
			if created.daysTo(QtCore.QDateTime.currentDateTime()):
				createdFormat = "MMM d,  %s" % createdFormat

			html = "<font color=\"#%s\">[%s]</font> " % (self.COLORS["time"], created.toLocalTime().toString(createdFormat))

			if alert:
				html += "<font color=\"#%s\">" % self.COLORS["alert"]
			else:
				html += "<font color=\"#%s\">" % self.COLORS["message"]

			if message.is_by_current_user():
				html += "<font color=\"#%s\">" % self.COLORS["nickSelf"]
			elif alert:
			 	html += "<font color=\"#%s\">" % self.COLORS["nickAlert"]
			else:
				html += "<font color=\"#%s\">" % self.COLORS["nick"]

			html += "%s" % ("<strong>%s</strong>" % user if alert else user)
			html += "</font>: "
			html += body
			html += "</font>"
		elif message.is_upload():
			html = "<font color=\"#%s\">" % self.COLORS["upload"]
			html += "<strong>%s</strong> uploaded <a href=\"%s\">%s</a>" % (
				user,
				message.upload["url"],
				message.upload["name"]
			)
			html += "</font>"
		elif message.is_topic_change():
			html = "<font color=\"#%s\">" % self.COLORS["leave"]
			html += "%s changed topic to <strong>%s</strong>" % (user, message.body)
			html += "</font>"

		if html:
			html = "%s<br />" % html
			editor = self._rooms[room.id]["editor"]
			if not editor:
				return

			scrollbar = editor.verticalScrollBar()
			currentScrollbarValue = scrollbar.value()
			autoScroll = (currentScrollbarValue == scrollbar.maximum())
			editor.moveCursor(QtGui.QTextCursor.End)
			editor.textCursor().insertHtml(html)
			if autoScroll:
				scrollbar.setValue(scrollbar.maximum())
			else:
				scrollbar.setValue(currentScrollbarValue)

			tabIndex = self._rooms[room.id]["tab"]
			tabBar = self._tabs.tabBar()
			isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex())

			if message.is_text() and not isActiveTab:
				self._rooms[room.id]["newMessages"] += 1

			if self._rooms[room.id]["newMessages"] > 0:
				tabBar.setTabText(tabIndex, "%s (%s)" % (room.name, self._rooms[room.id]["newMessages"]))

			if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["tabs"]["normal"]:
				tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["alert" if alert else "new"])

			if alert:
				if not isActiveTab:
					self._trayIcon.alert()
				if notify:
					self._notify(room, message.body)

		if updateRoom:
			if (message.is_joining() or message.is_leaving()):
				self.updateRoomUsers(room.id)
			elif message.is_upload():
				self.updateRoomUploads(room.id)
			elif message.is_topic_change() and not message.is_by_current_user():
				self._cfTopicChanged(room, message.body)

	def _matchesAlert(self, message):
		matches = False
		regexes = []
		words = [
			"Mariano Iglesias",
			"Mariano"
		]
		for word in words:
			regexes.append("\\b%s\\b" % word)

		for regex in regexes:
			if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)):
				matches = True
				break
		return matches

	def _cfConnected(self, user, rooms):
		self._connecting = False
		self._connected = True
		self._rooms = {}

		self._toolBar["rooms"].clear()
		for room in rooms:
			self._toolBar["rooms"].addItem(room["name"], room)

		self.statusBar().showMessage(self._("%s connected to Campfire") % user.name, 5000)
		self._updateLayout()

		if self.getSetting("connection", "join"):
			rooms = self.getSetting("connection", "rooms")
			if rooms:
				for roomId in rooms.split(","):
					count = self._toolBar["rooms"].count()
					if count:
						roomIndex = None
						for i in range(count):
							data = self._toolBar["rooms"].itemData(i)
							if not data.isNull():
								data = data.toMap()
								for key in data:
									if str(key) == "id" and str(data[key].toString()) == roomId:
										roomIndex = i
										break;
								if roomIndex is not None:
									break
						if roomIndex is not None:
							self.joinRoom(roomIndex)

	def _cfDisconnected(self):
		self._connecting = False
		self._connected = False
		self._rooms = {}
		self._worker = None
		self.statusBar().clearMessage()

	def _cfRoomJoined(self, room, messages=[]):
		if room.id not in self._rooms:
			return
		self._rooms[room.id].update(self._setupRoomUI(room))
		self._rooms[room.id]["room"] = room
		self._rooms[room.id]["stream"] = self._worker.getStream(room)
		self.updateRoomUsers(room.id)
		self.updateRoomUploads(room.id)
		self.statusBar().showMessage(self._("Joined room %s") % room.name, 5000)
		self._updatedRoomsList()
		if messages:
			for message in messages:
				self._cfStreamMessage(room, message, live=False, updateRoom=False)

	def _cfSpoke(self, room, message):
		self._cfStreamMessage(room, message, live=False)
		self.statusBar().clearMessage()

	def _cfRoomLeft(self, room):
		if self._rooms[room.id]["stream"]:
			self._rooms[room.id]["stream"].stop().join()
		if self._rooms[room.id]["upload"]:
			self._rooms[room.id]["upload"].stop().join()

		self._tabs.removeTab(self._rooms[room.id]["tab"])
		del self._rooms[room.id]
		self.statusBar().showMessage(self._("Left room %s") % room.name, 5000)
		self._updatedRoomsList()

	def _cfRoomUsers(self, room, users):
		# We may be disconnecting while still processing the list
		if not room.id in self._rooms:
			return

		self.statusBar().clearMessage()
		self._rooms[room.id]["usersList"].clear()
		for user in users:
			item = QtGui.QListWidgetItem(user["name"])
			item.setData(QtCore.Qt.UserRole, user)
			self._rooms[room.id]["usersList"].addItem(item)

	def _cfRoomUploads(self, room, uploads):
		# We may be disconnecting while still processing the list
		if not room.id in self._rooms:
			return

		self.statusBar().clearMessage()
		label = self._rooms[room.id]["filesLabel"]
		if uploads:
			html = ""
			for upload in uploads:
				html += "%s&bull; <a href=\"%s\">%s</a>" % (
					"<br />" if html else "",
					upload["full_url"],
					upload["name"]
				)
			html = "%s<br />%s" % (
				self._("Latest uploads:"),
				html
			)

			label.setText(html)
			if not label.isVisible():
				label.show()
		elif label.isVisible():
			label.setText("")
			label.hide()

	def _cfUploadProgress(self, room, current, total):
		if not room.id in self._rooms:
			return
		
		progressBar = self._rooms[room.id]["uploadProgressBar"]
		if not self._rooms[room.id]["uploadWidget"].isVisible():
			self._rooms[room.id]["uploadWidget"].show()
			progressBar.setMaximum(total)

		progressBar.setValue(current)

	def _cfUploadFinished(self, room):
		if not room.id in self._rooms:
			return

		self._rooms[room.id]["upload"].join()
		self._rooms[room.id]["upload"] = None
		self._rooms[room.id]["uploadWidget"].hide()

	def _cfTopicChanged(self, room, topic):
		if not room.id in self._rooms:
			return
		
		self._rooms[room.id]["topicLabel"].setText(topic)
		self.statusBar().clearMessage()

	def _cfConnectError(self, error):
		self._cfDisconnected()
		self._updateLayout()
		self._cfError(error)

	def _cfError(self, error):
		self.statusBar().clearMessage()
		QtGui.QMessageBox.critical(self, "Error", str(error))

	def _roomSelected(self, index):
		self._updatedRoomsList(index)

	def _upload(self, room, path):
		self._rooms[room.id]["upload"] = self._worker.upload(room, path)
		self._updateRoomLayout()

	def _roomTabClose(self, tabIndex):
		for roomId in self._rooms:
			if self._rooms[roomId]["tab"] == tabIndex:
				self.leaveRoom(roomId)
				break

	def _roomTabFocused(self):
		tabIndex = self._tabs.currentIndex()
		if tabIndex < 0 or not self.isActiveWindow():
			return

		room = self._roomInTabIndex(tabIndex)
		if not room:
			return

		tabBar = self._tabs.tabBar()

		if self._rooms[room.id]["newMessages"] > 0:
			self._rooms[room.id]["newMessages"] = 0
			tabBar.setTabText(tabIndex, room.name)

		if tabBar.tabTextColor(tabIndex) != self.COLORS["tabs"]["normal"]:
			tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["normal"])

		self._updateRoomLayout()

	def _roomInTabIndex(self, index):
		room = None
		for key in self._rooms:
			if self._rooms[key]["tab"] == index:
				room = self._rooms[key]["room"]
				break
		return room

	def _roomInIndex(self, index):
		room = {}
		data = self._toolBar["rooms"].itemData(index)
		if not data.isNull():
			data = data.toMap()
			for key in data:
				room[str(key)] = u"%s" % data[key].toString()
		return room

	def _connectWorkerSignals(self, worker):
		self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError)
		self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected)
		self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError)
		self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined)
		self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke)
		self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage)
		self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft)
		self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers)
		self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads)
		self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress)
		self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished)
		self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged)

	def _getWorker(self):
		if not hasattr(self, "_workers"):
			self._workers = []

		if self._workers:
			for worker in self._workers:
				if worker.isFinished():
					return worker

		worker = copy.copy(self._worker)
		self._connectWorkerSignals(worker)
		self._workers.append(worker)
		return worker

	def _updatedRoomsList(self, index=None):
		if not index:
			index = self._toolBar["rooms"].currentIndex()

		room = self._roomInIndex(index)

		self._toolBar["join"].setEnabled(False)
		if not room or room["id"] not in self._rooms:
			self._toolBar["join"].setEnabled(True)

		centralWidget = self.centralWidget()
		if not self._tabs.count():
			centralWidget.hide()
		else:
			centralWidget.show()

	def _notify(self, room, message):
		raise NotImplementedError("_notify() must be implemented")

	def _updateRoomLayout(self):
		room = self.getCurrentRoom()
		if room:
			canUpload = not self._rooms[room.id]["upload"]
			uploadButton = self._rooms[room.id]["uploadButton"]
			if (
				(canUpload and not uploadButton.isEnabled()) or
				(not canUpload and uploadButton.isEnabled())
			):
				uploadButton.setEnabled(canUpload)

	def _updateLayout(self):
		self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting)
		self._menus["file"]["disconnect"].setEnabled(self._connected)

		roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull()
		if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()):
			self._toolBar["rooms"].clear()
			self._toolBar["rooms"].addItem(self._("No rooms available"))
			self._toolBar["rooms"].setEnabled(False)
		elif not roomsEmpty:
			self._toolBar["rooms"].setEnabled(True)

		self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled())
		self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled())

	def _setupRoomUI(self, room):
		topicLabel = ClickableQLabel(room.topic)
		topicLabel.setToolTip(self._("Click here to change room's topic"))
		topicLabel.setWordWrap(True)
		self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic)

		editor = QtGui.QTextBrowser()
		editor.setOpenExternalLinks(True)

		usersList = QtGui.QListWidget()

		filesLabel = QtGui.QLabel("")
		filesLabel.setOpenExternalLinks(True)
		filesLabel.setWordWrap(True)
		filesLabel.hide()

		uploadButton = QtGui.QPushButton(self._("&Upload new file"))
		self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile)

		uploadProgressBar = QtGui.QProgressBar()
		uploadProgressLabel = QtGui.QLabel(self._("Uploading:"))

		uploadCancelButton = QtGui.QPushButton(self._("Cancel"))
		self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel)

		uploadLayout = QtGui.QHBoxLayout()
		uploadLayout.addWidget(uploadProgressLabel)
		uploadLayout.addWidget(uploadProgressBar)
		uploadLayout.addWidget(uploadCancelButton)

		uploadWidget = QtGui.QWidget()
		uploadWidget.setLayout(uploadLayout)
		uploadWidget.hide()

		leftFrameLayout = QtGui.QVBoxLayout()
		leftFrameLayout.addWidget(topicLabel)
		leftFrameLayout.addWidget(editor)
		leftFrameLayout.addWidget(uploadWidget)

		rightFrameLayout = QtGui.QVBoxLayout()
		rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:")))
		rightFrameLayout.addWidget(usersList)
		rightFrameLayout.addWidget(filesLabel)
		rightFrameLayout.addWidget(uploadButton)
		rightFrameLayout.addStretch(1)

		leftFrame = QtGui.QWidget()
		leftFrame.setLayout(leftFrameLayout)

		rightFrame = QtGui.QWidget()
		rightFrame.setLayout(rightFrameLayout)

		splitter = QtGui.QSplitter()
		splitter.addWidget(leftFrame)
		splitter.addWidget(rightFrame)
		splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25])

		index = self._tabs.addTab(splitter, room.name)
		self._tabs.setCurrentIndex(index)

		if not self.COLORS["tabs"]["normal"]:
			self.COLORS["tabs"]["normal"] = self._tabs.tabBar().tabTextColor(index)
		else:
			self._tabs.tabBar().setTabTextColor(index, self.COLORS["tabs"]["normal"])

		return {
			"tab": index,
			"editor": editor,
			"usersList": usersList,
			"topicLabel": topicLabel,
			"filesLabel": filesLabel,
			"uploadButton": uploadButton,
			"uploadWidget": uploadWidget,
			"uploadProgressBar": uploadProgressBar,
			"uploadProgressLabel": uploadProgressLabel
		}

	def _setupUI(self):
		self.setWindowTitle(self.NAME)

		self._addMenu()
		self._addToolbar()

		self._tabs = QtGui.QTabWidget()
		self._tabs.setTabsClosable(True)
		self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused)
		self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose)

		self._editor = QtGui.QPlainTextEdit()
		self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2)
		self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor)))

		speakButton = QtGui.QPushButton(self._("&Send"))
		self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

		grid = QtGui.QGridLayout()
		grid.setRowStretch(0, 1)
		grid.addWidget(self._tabs, 0, 0, 1, -1)
		grid.addWidget(self._editor, 1, 0)
		grid.addWidget(speakButton, 1, 1)

		widget = QtGui.QWidget()
		widget.setLayout(grid)
		self.setCentralWidget(widget)

		tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
		self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused)
		widget.installEventFilter(tabWidgetFocusEventFilter)

		self.centralWidget().hide()

		size = self.getSetting("window", "size")

		if not size:
			size = QtCore.QSize(640, 480)

		self.resize(size)

		position = self.getSetting("window", "position")
		if not position:
			screen = QtGui.QDesktopWidget().screenGeometry()
			position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2)

		self.move(position)

		self._updateLayout()

		menu = QtGui.QMenu(self)
		menu.addAction(self._menus["file"]["connect"])
		menu.addAction(self._menus["file"]["disconnect"])
		menu.addSeparator()
		menu.addAction(self._menus["file"]["exit"])

		self._trayIcon = Systray(self._icon, self)
		self._trayIcon.setContextMenu(menu)
		self._trayIcon.setToolTip(self.DESCRIPTION)

	def _addMenu(self):
		self._menus = {
			"file": {
				"connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"),
				"disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"),
				"exit": self._createAction(self._("E&xit"), self.exit)
			},
			"settings": {
				"alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"),
				"options": self._createAction(self._("&Options..."), self.options)
			},
			"help": {
				"about": self._createAction(self._("A&bout"))
			}
		}

		menu = self.menuBar()

		file_menu = menu.addMenu(self._("&File"))
		file_menu.addAction(self._menus["file"]["connect"])
		file_menu.addAction(self._menus["file"]["disconnect"])
		file_menu.addSeparator()
		file_menu.addAction(self._menus["file"]["exit"])

		settings_menu = menu.addMenu(self._("S&ettings"))
		settings_menu.addAction(self._menus["settings"]["alerts"])
		settings_menu.addSeparator()
		settings_menu.addAction(self._menus["settings"]["options"])

		help_menu = menu.addMenu(self._("&Help"))
		help_menu.addAction(self._menus["help"]["about"])

	def _addToolbar(self):
		self._toolBar = {
			"connect": self._menus["file"]["connect"],
			"disconnect": self._menus["file"]["disconnect"],
			"roomsLabel": QtGui.QLabel(self._("Rooms:")),
			"rooms": QtGui.QComboBox(),
			"join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"),
			"alerts": self._menus["settings"]["alerts"]
		}

		self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected)

		toolBar = self.toolBar()
		toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
		toolBar.addAction(self._toolBar["connect"])
		toolBar.addAction(self._toolBar["disconnect"])
		toolBar.addSeparator();
		toolBar.addWidget(self._toolBar["roomsLabel"])
		toolBar.addWidget(self._toolBar["rooms"])
		toolBar.addAction(self._toolBar["join"])
		toolBar.addSeparator();
		toolBar.addAction(self._toolBar["alerts"])

	def _createAction(self, text, slot=None, shortcut=None, icon=None, 
		tip=None, checkable=False, signal="triggered()"):
		""" Create an action """
		action = QtGui.QAction(text, self) 
		if icon is not None:
			if not isinstance(icon, QtGui.QIcon):
				action.setIcon(QtGui.QIcon(":/icons/%s" % (icon)))
			else:
				action.setIcon(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, QtCore.SIGNAL(signal), slot) 
		if checkable: 
			action.setCheckable(True)
		return action

	def _plainTextToHTML(self, string):
		return string.replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />")

	def _autoLink(self, string):
		urlre = re.compile("(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?")
		urls = urlre.findall(string)
		cleanUrls = []
		for url in urls:
			if url[1]:
				continue

			currentUrl = url[0]
			if currentUrl[0] == '(' and currentUrl[-1] == ')':
				currentUrl = currentUrl[1:-1]

			if currentUrl in cleanUrls:
				continue

			cleanUrls.append(currentUrl)
			string = re.sub("(?<!(=\"|\">))" + re.escape(currentUrl),
							"<a href=\"" + currentUrl + "\">" + currentUrl + "</a>",
							string)
		return string
Example #13
0
class ScudCloud(QtGui.QMainWindow):

    APP_NAME = "ScudCloud Slack_SSB"
    SIGNIN_URL = "https://slack.com/signin"
    debug = False
    forceClose = False
    messages = 0

    def __init__(self, parent = None, settings_path = "~/.config/scudcloud"):
        super(ScudCloud, self).__init__(parent)
        self.setWindowTitle('ScudCloud')
        self.settings_path = settings_path
        self.notifier = Notifier(self.APP_NAME, get_resource_path('scudcloud.png'))
        self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.leftPane = LeftPane(self)
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        self.stackedWidget = QtGui.QStackedWidget()
        self.stackedWidget.addWidget(webView)
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(ScudCloud.minimized)
        self.installEventFilter(self)
        if self.identifier is None:
            webView.load(QtCore.QUrl(self.SIGNIN_URL))
        else:
            webView.load(QtCore.QUrl(self.domain()))
        webView.show()

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addMenu(self):
        self.menus = {
            "file": {
                "preferences": self.createAction("Preferences", self.current().preferences),
                "systray":     self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":     self.createAction("Sign in to Another Team", self.current().addTeam),
                "signout":     self.createAction("Signout", self.current().logout),
                "close":       self.createAction("Close", self.close, QKeySequence.Close),
                "exit":        self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo":        self.current().pageAction(QtWebKit.QWebPage.Undo),
                "redo":        self.current().pageAction(QtWebKit.QWebPage.Redo),
                "cut":         self.current().pageAction(QtWebKit.QWebPage.Cut),
                "copy":        self.current().pageAction(QtWebKit.QWebPage.Copy),
                "paste":       self.current().pageAction(QtWebKit.QWebPage.Paste),
                "reload":      self.current().pageAction(QtWebKit.QWebPage.Reload)
            },
            "view": {
                "zoomin":      self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":     self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut),
                "reset":       self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0)
            },
            "help": {
                "help":       self.createAction("Help and Feedback", self.current().help, QKeySequence.HelpContents),
                "center":     self.createAction("Slack Help Center", self.current().helpCenter),
                "about":      self.createAction("About", self.current().about)
             }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        if shortcut is not None:
            action.setShortcut(shortcut)
        action.triggered.connect(slot)
        if checkable:
            action.setCheckable(True)
        return action

    def domain(self):
        if self.identifier.endswith(".slack.com"):
            return self.identifier
        else:
            return "https://"+self.identifier+".slack.com"

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if teams is not None and len(teams) > 1:
            self.leftPane.show()
            for t in teams:
                try:
                    self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_88'], t == teams[0])
                except:
                    self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], '', t == teams[0])

    def switchTo(self, url):
        index = -1
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                index = i
                break
        if index != -1:
            self.stackedWidget.setCurrentIndex(index)
        else:
            webView = Wrapper(self)
            webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
            webView.load(QtCore.QUrl(url))
            webView.show()
            self.stackedWidget.addWidget(webView)
            self.stackedWidget.setCurrentWidget(webView)
        self.quicklist(self.current().listChannels())
        self.enableMenus(self.current().isConnected())

    def eventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress and QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
            if event.key() == QtCore.Qt.Key_1:   self.leftPane.click(0)
            elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
            elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
            elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
            elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
            elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
            elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
            elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
            elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8)
        return QtGui.QMainWindow.eventFilter(self, obj, event);

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.forceClose = True
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            ql = Dbusmenu.Menuitem.new()
            self.launcher.set_property("quicklist", ql)
            if channels is not None:
                for c in channels:
                    if c['is_member']:
                        item = Dbusmenu.Menuitem.new ()
                        item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name'])
                        item.property_set ("id", c['name'])
                        item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message):
        self.notifier.notify(title, message)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()

    def count(self):
        total = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            if widget.messages == 0:
                self.leftPane.stopAlert(widget.team())
            else:
                self.leftPane.alert(widget.team())
            total+=widget.messages
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
        self.messages = total
Example #14
0
class Snakefire(object):
    DOMAIN = "www.snakefire.org"
    NAME = "Snakefire"
    DESCRIPTION = "Snakefire: Campfire Linux Client"
    VERSION = "1.0.3"
    ICON = "snakefire.png"
    COLORS = {
        "normal": None,
        "new": QtGui.QColor(0, 0, 255),
        "alert": QtGui.QColor(255, 0, 0)
    }

    def __init__(self):
        self.DESCRIPTION = self._(self.DESCRIPTION)
        self._pingTimer = None
        self._idleTimer = None
        self._idle = False
        self._lastIdleAnswer = None
        self._worker = None
        self._settings = {}
        self._canConnect = False
        self._cfDisconnected()

        if len(sys.argv) > 1:
            self._qsettings = QtCore.QSettings(sys.argv[1], QtCore.QSettings.IniFormat if sys.platform.find("win") == 0 else QtCore.QSettings.NativeFormat)
        else:
            self._qsettings = QtCore.QSettings(self.NAME, self.NAME)

        self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON))
        self.setWindowIcon(self._icon)
        self.setAcceptDrops(True)
        self._setupUI()

        settings = self.getSettings("connection")

        self._canConnect = False
        if settings["subdomain"] and settings["user"] and settings["password"]:
            self._canConnect = True

        self._updateLayout()

        if not self._canConnect:
            self.options()
        elif settings["connect"]:
            self.connectNow()

    def showEvent(self, event):
        if self._trayIcon.isVisible():
            if self._trayIcon.isAlerting():
                self._trayIcon.stopAlert()
            return
        self._trayIcon.show()

    def dragEnterEvent(self, event):
        room = self.getCurrentRoom()
        canUpload = not self._rooms[room.id]["upload"] if room else False
        if canUpload and self._getDropFile(event):
            event.acceptProposedAction()

    def dropEvent(self, event):
        room = self.getCurrentRoom()
        path = self._getDropFile(event)
        if room and path:
            self._upload(room, path)

    def _getDropFile(self, event):
        files = []
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                path = url.path()
                if path and os.path.exists(path) and os.path.isfile(path):
                    try:
                        handle = open(str(path))
                        handle.close()
                        files.append(str(path))
                    except:
                        pass
            if len(files) > 1:
               files = []
        return files[0] if files else None

    def getSetting(self, group, setting):
        settings = self.getSettings(group, asString=False);
        return settings[setting] if setting in settings else None

    def setSetting(self, group, setting, value):
        self._qsettings.beginGroup(group);
        self._qsettings.setValue(setting, value)
        self._qsettings.endGroup();

    def getSettings(self, group, asString=True, reload=False):
        defaults = {
            "connection": {
                "subdomain": None,
                "user": None,
                "password": None,
                "ssl": False,
                "connect": False,
                "join": False,
                "rooms": []
            },
            "program": {
                "minimize": False,
                "spell_language": SpellTextEditor.defaultLanguage(),
                "away": True,
                "away_time": 10,
                "away_time_between_messages": 5,
                "away_message": self._("I am currently away from {name}").format(name=self.NAME)
            },
            "display": {
                "theme": "default",
                "size": 100,
                "show_join_message": True,
                "show_part_message": True,
                "show_message_timestamps": True
            },
            "alerts": {
                "notify_ping": True,
                "notify_inactive_tab": False,
                "notify_blink": True,
                "notify_notify": True
            },
            "matches": []
        }

        if reload or not group in self._settings:
            settings = defaults[group] if group in defaults else {}

            if group == "matches":
                settings = []
                size = self._qsettings.beginReadArray("matches")
                for i in range(size):
                    self._qsettings.setArrayIndex(i)
                    isRegex = False
                    try:
                        isRegex = True if ["true", "1"].index(str(self._qsettings.value("regex").toPyObject()).lower()) >= 0 else False
                    except:
                        pass

                    settings.append({
                        'regex': isRegex,
                        'match': self._qsettings.value("match").toPyObject()
                    })
                self._qsettings.endArray()
            else:
                self._qsettings.beginGroup(group);
                for setting in self._qsettings.childKeys():
                    settings[str(setting)] = self._qsettings.value(setting).toPyObject()
                self._qsettings.endGroup();

                boolSettings = []
                if group == "connection":
                    boolSettings += ["ssl", "connect", "join"]
                elif group == "program":
                    boolSettings += ["away", "minimize"]
                elif group == "display":
                    boolSettings += ["show_join_message", "show_part_message", "show_message_timestamps"]
                elif group == "alerts":
                    boolSettings += ["notify_ping", "notify_inactive_tab", "notify_blink", "notify_notify"]

                for boolSetting in boolSettings:
                    try:
                        settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False
                    except:
                        settings[boolSetting] = False

                if group == "connection" and settings["subdomain"] and settings["user"]:
                    settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"]))

            self._settings[group] = settings

        settings = self._settings[group]
        if asString:
            if isinstance(settings, list):
                for i, row in enumerate(settings):
                    for setting in row:
                        if not isinstance(row[setting], bool):
                            settings[i][setting] = str(row[setting]) if row[setting] else ""
            else:
                for setting in settings:
                    if not isinstance(settings[setting], bool):
                        settings[setting] = str(settings[setting]) if settings[setting] else ""

        return settings

    def setSettings(self, group, settings):
        self._settings[group] = settings;

        if group == "matches":
            self._qsettings.beginWriteArray("matches")
            for i, setting in enumerate(settings):
                self._qsettings.setArrayIndex(i)
                self._qsettings.setValue("regex", setting["regex"])
                self._qsettings.setValue("match", setting["match"])
            self._qsettings.endArray()
        else:
            self._qsettings.beginGroup(group);
            for setting in self._settings[group]:
                if group != "connection" or setting != "password":
                    self._qsettings.setValue(setting, settings[setting])
                elif settings["subdomain"] and settings["user"]:
                    keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting])
            self._qsettings.endGroup();

            if group == "connection":
                self._canConnect = False
                if settings["subdomain"] and settings["user"] and settings["password"]:
                    self._canConnect = True
                self._updateLayout()
            elif group == "program":
                if settings["away"] and self._connected:
                    self._setUpIdleTracker()
                else:
                    self._setUpIdleTracker(False)
                if self._editor:
                    if settings["spell_language"]:
                        self._editor.enableSpell(settings["spell_language"])
                    else:
                        self._editor.disableSpell()
            elif group == "display":
                for roomId in self._rooms.keys():
                    if roomId in self._rooms and self._rooms[roomId]["view"]:
                        self._rooms[roomId]["view"].updateTheme(settings["theme"], settings["size"])

    def exit(self):
        self._forceClose = True
        self.close()

    def changeEvent(self, event):
        if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized():
            self.hide()
            event.ignore()
        else:
            event.accept()

    def closeEvent(self, event):
        if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"):
            self.hide()
            event.ignore()
        else:
            if self.getSetting("connection", "join"):
                self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()]))

            self.disconnectNow()

            if hasattr(self, "_workers") and self._workers:
                for worker in self._workers:
                    worker.terminate()
                    worker.wait()

            if hasattr(self, "_worker") and self._worker:
                self._worker.terminate()
                self._worker.wait()

            self.setSetting("window", "size", self.size())
            self.setSetting("window", "position", self.pos())

            event.accept()

    def alerts(self):
        dialog = AlertsDialog(self)
        dialog.open()

    def options(self):
        dialog = OptionsDialog(self)
        dialog.open()

    def about(self):
        dialog = AboutDialog(self)
        dialog.open()

    def connectNow(self):
        if not self._canConnect:
            return

        self._connecting = True
        self.statusBar().showMessage(self._("Connecting with Campfire..."))
        self._updateLayout()

        settings = self.getSettings("connection")

        self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self)
        self._connectWorkerSignals(self._worker)
        self._worker.connect()

    def disconnectNow(self):
        self.statusBar().showMessage(self._("Disconnecting from Campfire..."))
        if self._worker and hasattr(self, "_rooms"):
            # Using keys() since the dict could be changed (by _cfRoomLeft())
            # while iterating on it
            for roomId in self._rooms.keys():
                if roomId in self._rooms and self._rooms[roomId]["room"]:
                    self._worker.leave(self._rooms[roomId]["room"], False)

        self._cfDisconnected()
        self._updateLayout()

    def joinRoom(self, roomIndex=None):
        room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex())
        if not room:
            return

        self._toolBar["join"].setEnabled(False)
        self.statusBar().showMessage(unicode(self._("Joining room {room}...").format(room=room["name"])))

        self._rooms[room["id"]] = {
            "room": None,
            "stream": None,
            "upload": None,
            "tab": None,
            "view": None,
            "frame": None,
            "usersList": None,
            "topicLabel": None,
            "filesLabel": None,
            "uploadButton": None,
            "uploadLabel": None,
            "uploadWidget": None,
            "newMessages": 0
        }
        self._getWorker().join(room["id"])

    def ping(self):
        if not self._connected:
            return
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["room"]:
                self.updateRoomUsers(roomId, pinging=True)

    def speak(self):
        message = self._editor.document().toPlainText()
        room = self.getCurrentRoom()
        if not room or message.trimmed().isEmpty():
            return

        self._editor.document().clear()

        if message[0] == '/':
            command = QtCore.QString(message)
            separatorIndex = command.indexOf(QtCore.QRegExp('\\s'));
            handled = self.command(command.mid(1, separatorIndex-1), command.mid(separatorIndex + 1 if separatorIndex >= 0 else command.length()))
            if handled:
                return

        self.statusBar().showMessage(unicode(self._("Sending message to {room}...").format(room=room.name)))
        self._getWorker().speak(room, unicode(message))

    def command(self, command, args):
        if command.compare(QtCore.QString("away"), QtCore.Qt.CaseInsensitive) == 0:
            self.toggleAway()
            return True

    def uploadFile(self):
        room = self.getCurrentRoom()
        if not room:
            return

        path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload"))
        if path:
            self._upload(room, str(path))

    def uploadCancel(self):
        room = self.getCurrentRoom()
        if not room:
            return

        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()
            self._rooms[room.id]["upload"] = None

        self._rooms[room.id]["uploadWidget"].hide()

    def leaveRoom(self, roomId):
        if roomId in self._rooms:
            self.statusBar().showMessage(unicode(self._("Leaving room {room}...").format(room=self._rooms[roomId]["room"].name)))
            self._getWorker().leave(self._rooms[roomId]["room"])

    def changeTopic(self):
        room = self.getCurrentRoom()
        if not room:
            return
        topic, ok = QtGui.QInputDialog.getText(self,
            self._("Change topic"),
            unicode(self._("Enter new topic for room {room}").format(room=room.name)),
            QtGui.QLineEdit.Normal,
            room.topic
        )
        if ok:
            self.statusBar().showMessage(unicode(self._("Changing topic for room {room}...").format(room=room.name)))
            self._getWorker().changeTopic(room, topic)

    def updateRoomUsers(self, roomId = None, pinging = False):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            if not pinging:
                self.statusBar().showMessage(unicode(self._("Getting users in {room}...").format(room=self._rooms[roomId]["room"].name)))
            self._getWorker().users(self._rooms[roomId]["room"], pinging)

    def updateRoomUploads(self, roomId = None):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            self.statusBar().showMessage(unicode(self._("Getting uploads from {room}...").format(room=self._rooms[roomId]["room"].name)))
            self._getWorker().uploads(self._rooms[roomId]["room"])

    def getCurrentRoom(self):
        index = self._tabs.currentIndex()
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["tab"] == index:
                return self._rooms[roomId]["room"]

    def toggleAway(self):
        self.setAway(False if self._idle else True)

    def setAway(self, away=True):
        self._idle = away
        self.statusBar().showMessage(self._("You are now away") if self._idle else self._('You are now active'), 5000)

    def onIdle(self):
        self.setAway(True)

    def onActive(self):
        self.setAway(False)

    def _setUpIdleTracker(self, enable=True):
        if self._idleTimer:
            self._idleTimer.stop()
            self._idleTimer = None

        if enable and IdleTimer.supported():
            self._idleTimer = IdleTimer(self, int(self.getSetting("program", "away_time")) * 60)
            self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self.onIdle)
            self.connect(self._idleTimer, QtCore.SIGNAL("active()"), self.onActive)
            self._idleTimer.start()

    def _cfStreamMessage(self, room, message, live=True, updateRoom=True):
        if (
            not message.user or
            (live and message.is_text() and message.is_by_current_user()) or
            not room.id in self._rooms
        ):
            return

        view = self._rooms[room.id]["view"]
        if not view:
            return

        alert = False
        alertIsDirectPing = False

        if message.is_text() and not message.is_by_current_user():
            alertIsDirectPing = (QtCore.QString(message.body).indexOf(QtCore.QRegExp("\\s*\\b{name}\\b".format(name=QtCore.QRegExp.escape(self._worker.getUser().name)), QtCore.Qt.CaseInsensitive)) == 0)
            alert = self.getSetting("alerts", "notify_ping") if alertIsDirectPing else self._matchesAlert(message.body)

        maximumImageWidth = int(view.size().width() * 0.4) # 40% of viewport
        renderer = MessageRenderer(
            self._worker.getApiToken(),
            maximumImageWidth,
            room,
            message,
            live=live,
            updateRoom=updateRoom,
            showTimestamps = self.getSetting("display", "show_message_timestamps"),
            alert=alert,
            alertIsDirectPing=alertIsDirectPing,
            parent=self
        )

        if renderer.needsThread():
            self.connect(renderer, QtCore.SIGNAL("render(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._renderMessage)
            renderer.start()
        else:
            self._renderMessage(renderer.render(), room, message, live=live, updateRoom=updateRoom, alert=alert, alertIsDirectPing=alertIsDirectPing)

    def _renderMessage(self, html, room, message, live=True, updateRoom=True, alert=False, alertIsDirectPing=False):
        if (not room.id in self._rooms):
            return

        frame = self._rooms[room.id]["frame"]
        view = self._rooms[room.id]["view"]
        if not frame or not view:
            return

        if not self.getSetting("display", "show_join_message") and (message.is_joining() or message.is_leaving() or message.is_kick()):
            return

        if html:
            currentScrollbarValue = frame.scrollPosition()
            autoScroll = (currentScrollbarValue == frame.scrollBarMaximum(QtCore.Qt.Vertical))
            frame.setHtml(frame.toHtml() + html)
            view.show()
            if autoScroll:
                frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical))
            else:
                frame.scroll(currentScrollbarValue.x(), currentScrollbarValue.y())

            tabIndex = self._rooms[room.id]["tab"]
            tabBar = self._tabs.tabBar()
            isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex())

            if message.is_text() and not isActiveTab:
                self._rooms[room.id]["newMessages"] += 1

            if self._rooms[room.id]["newMessages"] > 0:
                tabBar.setTabText(tabIndex, unicode("{room} ({count})".format(room = room.name, count = self._rooms[room.id]["newMessages"])))

            if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]:
                tabBar.setTabTextColor(tabIndex, self.COLORS["alert" if alert else "new"])

            notifyInactiveTab = self.getSetting("alerts", "notify_inactive_tab")

            if (not isActiveTab and (alert or notifyInactiveTab)) and self.getSetting("alerts", "notify_blink"):
                self._trayIcon.alert()

            if live and ((alert or (not isActiveTab and notifyInactiveTab and message.is_text())) and self.getSetting("alerts", "notify_notify")):
                self._notify(room, unicode("{} says: {}".format(message.user.name, message.body)), message.user)

        if updateRoom:
            if (message.is_joining() or message.is_leaving()):
                self.updateRoomUsers(room.id)
            elif message.is_upload():
                self.updateRoomUploads(room.id)
            elif message.is_topic_change() and not message.is_by_current_user():
                self._cfTopicChanged(room, message.body)

        # Respond to direct pings while being away, but only send an auto-response if last one was sent more than 2 minutes ago
        if live and alertIsDirectPing and self.getSetting("program", "away") and self._idle:
            if self._lastIdleAnswer is None or time.time() - self._lastIdleAnswer >= (int(self.getSetting("program", "away_time_between_messages")) * 60):
                self._lastIdleAnswer = time.time()
                self._getWorker().speak(room, unicode("{user}: {message}".format(
                    user = message.user.name,
                    message = self.getSetting("program", "away_message")
                )))

    def _matchesAlert(self, message):
        matches = False
        searchMatches = self.getSettings("matches")
        for match in searchMatches:
            regex = "\\b{word}\\b".format(word=QtCore.QRegExp.escape(match['match'])) if not match['regex'] else match['match']
            if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)):
                matches = True
                break
        return matches

    def _cfConnected(self, user, rooms):
        self._connecting = False
        self._connected = True
        self._rooms = {}

        self._toolBar["rooms"].clear()
        for room in rooms:
            self._toolBar["rooms"].addItem(room["name"], room)

        self.statusBar().showMessage(unicode(self._("{user} connected to Campfire").format(user=user.name)), 5000)
        self._updateLayout()

        if not self._pingTimer:
            self._pingTimer = QtCore.QTimer(self)
            self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"), self.ping)
        self._pingTimer.start(60000) # Ping every minute

        if self.getSetting("program", "away"):
            self._setUpIdleTracker()

        if self.getSetting("connection", "join"):
            rooms = self.getSetting("connection", "rooms")
            if rooms:
                for roomId in rooms.split(","):
                    count = self._toolBar["rooms"].count()
                    if count:
                        roomIndex = None
                        for i in range(count):
                            data = self._toolBar["rooms"].itemData(i)
                            if not data.isNull():
                                data = data.toMap()
                                for key in data:
                                    if str(key) == "id" and str(data[key].toString()) == roomId:
                                        roomIndex = i
                                        break;
                                if roomIndex is not None:
                                    break
                        if roomIndex is not None:
                            self.joinRoom(roomIndex)

    def _cfDisconnected(self):
        if self._pingTimer:
            self._pingTimer.stop()
            self._pingTimer = None

        if self._idleTimer:
            self._setUpIdleTracker(False)

        self._connecting = False
        self._connected = False
        self._rooms = {}
        self._worker = None
        self.statusBar().clearMessage()

    def _cfRoomJoined(self, room, messages=[], rejoined=False):
        if room.id not in self._rooms:
            return

        if not rejoined:
            self._rooms[room.id].update(self._setupRoomUI(room))
            self._rooms[room.id]["room"] = room
        self._rooms[room.id]["stream"] = self._worker.getStream(room)
        self.updateRoomUsers(room.id)
        self.updateRoomUploads(room.id)
        if not rejoined:
            self.statusBar().showMessage(unicode(self._("Joined room {room}").format(room=room.name)), 5000)
        self._updatedRoomsList()
        if not rejoined and messages:
            for message in messages:
                self._cfStreamMessage(room, message, live=False, updateRoom=False)

    def _cfSpoke(self, room, message):
        self._cfStreamMessage(room, message, live=False)
        self.statusBar().clearMessage()

    def _cfRoomLeft(self, room):
        if self._rooms[room.id]["stream"]:
            self._rooms[room.id]["stream"].stop().join()
        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()

        self._tabs.removeTab(self._rooms[room.id]["tab"])
        del self._rooms[room.id]
        self.statusBar().showMessage(unicode(self._("Left room {room}").format(room=room.name)), 5000)
        self._updatedRoomsList()

    def _cfRoomUsers(self, room, users, pinging=False):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        if not pinging:
            self.statusBar().clearMessage()
        self._rooms[room.id]["usersList"].clear()
        for user in users:
            item = QtGui.QListWidgetItem(user["name"])
            item.setData(QtCore.Qt.UserRole, user)
            self._rooms[room.id]["usersList"].addItem(item)

    def _cfRoomUploads(self, room, uploads):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        self.statusBar().clearMessage()
        label = self._rooms[room.id]["filesLabel"]
        if uploads:
            html = ""
            for upload in uploads:
                html += "{br}&bull; <a href=\"{url}\">{name}</a>".format(
                    br = "<br />" if html else "",
                    url = upload["full_url"],
                    name = upload["name"]
                )
            html = unicode("{text}<br />{html}".format(
                text = self._("Latest uploads:"),
                html = html
            ))

            label.setText(html)
            if not label.isVisible():
                label.show()
        elif label.isVisible():
            label.setText("")
            label.hide()

    def _cfUploadProgress(self, room, current, total):
        if not room.id in self._rooms:
            return

        progressBar = self._rooms[room.id]["uploadProgressBar"]
        if not self._rooms[room.id]["uploadWidget"].isVisible():
            self._rooms[room.id]["uploadWidget"].show()
            progressBar.setMaximum(total)

        progressBar.setValue(current)

    def _cfUploadFinished(self, room):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["upload"].join()
        self._rooms[room.id]["upload"] = None
        self._rooms[room.id]["uploadWidget"].hide()

    def _cfTopicChanged(self, room, topic):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["topicLabel"].setText(topic)
        self.statusBar().clearMessage()

    def _cfConnectError(self, error):
        self._cfDisconnected()
        self._updateLayout()
        self._cfError(error)

    def _cfError(self, error):
        self.statusBar().clearMessage()
        if not self._connected:
            QtGui.QMessageBox.critical(self, "Error", self._("Error while connecting: {error}".format(error = str(error))))
        else:
            QtGui.QMessageBox.critical(self, "Error", str(error))

    def _cfRoomError(self, error, room):
        self.statusBar().clearMessage()
        if isinstance(error, RuntimeError):
            (code, message) = error
            if code == 401:
                self.statusBar().showMessage(unicode(self._("Disconnected from room. Rejoining room {room}...").format(room=room.name)), 5000)
                self._rooms[room.id]["stream"].stop().join()
                self._getWorker().join(room.id, True)
                return
        QtGui.QMessageBox.critical(self, "Error", str(error))

    def _roomSelected(self, index):
        self._updatedRoomsList(index)

    def _upload(self, room, path):
        self._rooms[room.id]["upload"] = self._worker.upload(room, path)
        self._updateRoomLayout()

    def _roomTabClose(self, tabIndex):
        for roomId in self._rooms:
            if self._rooms[roomId]["tab"] == tabIndex:
                self.leaveRoom(roomId)
                break

    def _roomTabFocused(self):
        tabIndex = self._tabs.currentIndex()
        if tabIndex < 0 or not self.isActiveWindow():
            return

        room = self._roomInTabIndex(tabIndex)
        if not room:
            return

        tabBar = self._tabs.tabBar()

        if self._rooms[room.id]["newMessages"] > 0:
            self._rooms[room.id]["newMessages"] = 0
            tabBar.setTabText(tabIndex, room.name)

        if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]:
            tabBar.setTabTextColor(tabIndex, self.COLORS["normal"])

        self._updateRoomLayout()

    def _roomInTabIndex(self, index):
        room = None
        for key in self._rooms:
            if self._rooms[key]["tab"] == index:
                room = self._rooms[key]["room"]
                break
        return room

    def _roomInIndex(self, index):
        room = {}
        data = self._toolBar["rooms"].itemData(index)
        if not data.isNull():
            data = data.toMap()
            for key in data:
                room[str(key)] = unicode(data[key].toString())
        return room

    def _connectWorkerSignals(self, worker):
        self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError)
        self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected)
        self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError)
        self.connect(worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError)
        self.connect(worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError)
        self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined)
        self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke)
        self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage)
        self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft)
        self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers)
        self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads)
        self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress)
        self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished)
        self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged)

    def _getWorker(self):
        if not hasattr(self, "_workers"):
            self._workers = []

        if self._workers:
            for worker in self._workers:
                if worker.isFinished():
                    return worker

        worker = copy.copy(self._worker)
        self._connectWorkerSignals(worker)
        self._workers.append(worker)
        return worker

    def _updatedRoomsList(self, index=None):
        if not index:
            index = self._toolBar["rooms"].currentIndex()

        room = self._roomInIndex(index)

        self._toolBar["join"].setEnabled(False)
        if not room or room["id"] not in self._rooms:
            self._toolBar["join"].setEnabled(True)

        centralWidget = self.centralWidget()
        if not self._tabs.count():
            centralWidget.hide()
        else:
            centralWidget.show()

    def _notify(self, room, message, user):
        raise NotImplementedError("_notify() must be implemented")

    def _updateRoomLayout(self):
        room = self.getCurrentRoom()
        if room:
            canUpload = not self._rooms[room.id]["upload"]
            uploadButton = self._rooms[room.id]["uploadButton"]
            if (
                (canUpload and not uploadButton.isEnabled()) or
                (not canUpload and uploadButton.isEnabled())
            ):
                uploadButton.setEnabled(canUpload)

    def _updateLayout(self):
        self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting)
        self._menus["file"]["disconnect"].setEnabled(self._connected)

        roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull()
        if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()):
            self._toolBar["rooms"].clear()
            self._toolBar["rooms"].addItem(self._("No rooms available"))
            self._toolBar["rooms"].setEnabled(False)
        elif not roomsEmpty:
            self._toolBar["rooms"].setEnabled(True)

        self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled())
        self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled())

    def _setupRoomUI(self, room):
        topic = room.topic if room.topic else ""
        topicLabel = ClickableQLabel(topic)
        topicLabel.setToolTip(self._("Click here to change room's topic"))
        topicLabel.setWordWrap(True)
        self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic)

        view = SnakeFireWebView(self)
        view.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        frame = view.page().mainFrame()

        #Send all link clicks to systems web browser
        view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
        def linkClicked(url):
            QtGui.QDesktopServices.openUrl(url)
        view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"), linkClicked)

        # Support auto scroll when needed
        def autoScroll(size):
            frame.scroll(0, size.height())
        frame.connect(frame, QtCore.SIGNAL("contentsSizeChanged (const QSize&)"), autoScroll)

        usersList = QtGui.QListWidget()

        filesLabel = QtGui.QLabel("")
        filesLabel.setOpenExternalLinks(True)
        filesLabel.setWordWrap(True)
        filesLabel.hide()

        uploadButton = QtGui.QPushButton(self._("&Upload new file"))
        self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile)

        uploadProgressBar = QtGui.QProgressBar()
        uploadProgressLabel = QtGui.QLabel(self._("Uploading:"))

        uploadCancelButton = QtGui.QPushButton(self._("Cancel"))
        self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel)

        uploadLayout = QtGui.QHBoxLayout()
        uploadLayout.addWidget(uploadProgressLabel)
        uploadLayout.addWidget(uploadProgressBar)
        uploadLayout.addWidget(uploadCancelButton)

        uploadWidget = QtGui.QWidget()
        uploadWidget.setLayout(uploadLayout)
        uploadWidget.hide()

        leftFrameLayout = QtGui.QVBoxLayout()
        leftFrameLayout.addWidget(topicLabel)
        leftFrameLayout.addWidget(view)
        leftFrameLayout.addWidget(uploadWidget)

        rightFrameLayout = QtGui.QVBoxLayout()
        rightFrameLayout.addWidget(usersList)
        rightFrameLayout.addWidget(filesLabel)
        rightFrameLayout.addWidget(uploadButton)
        rightFrameLayout.addStretch(1)

        leftFrame = QtGui.QWidget()
        leftFrame.setLayout(leftFrameLayout)

        rightFrame = QtGui.QWidget()
        rightFrame.setLayout(rightFrameLayout)

        splitter = QtGui.QSplitter()
        splitter.addWidget(leftFrame)
        splitter.addWidget(rightFrame)
        splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25])

        index = self._tabs.addTab(splitter, room.name)
        self._tabs.setCurrentIndex(index)

        if not self.COLORS["normal"]:
            self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index)
        else:
            self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"])

        return {
            "tab": index,
            "view": view,
            "frame": frame,
            "usersList": usersList,
            "topicLabel": topicLabel,
            "filesLabel": filesLabel,
            "uploadButton": uploadButton,
            "uploadWidget": uploadWidget,
            "uploadProgressBar": uploadProgressBar,
            "uploadProgressLabel": uploadProgressLabel
        }

    def _setupUI(self):
        self.setWindowTitle(self.NAME)

        self._addMenu()
        self._addToolbar()

        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused)
        self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose)

        self._editor = SpellTextEditor(lang=self.getSetting("program", "spell_language"), mainFrame=self)

        speakButton = QtGui.QPushButton(self._("&Send"))
        self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

        grid = QtGui.QGridLayout()
        grid.setRowStretch(0, 1)
        grid.addWidget(self._tabs, 0, 0, 1, -1)
        grid.addWidget(self._editor, 2, 0)
        grid.addWidget(speakButton, 2, 1)

        widget = QtGui.QWidget()
        widget.setLayout(grid)
        self.setCentralWidget(widget)

        tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
        self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused)
        widget.installEventFilter(tabWidgetFocusEventFilter)

        self.centralWidget().hide()

        size = self.getSetting("window", "size")

        if not size:
            size = QtCore.QSize(640, 480)

        self.resize(size)

        position = self.getSetting("window", "position")
        if not position:
            screen = QtGui.QDesktopWidget().screenGeometry()
            position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2)

        self.move(position)

        self._updateLayout()

        menu = QtGui.QMenu(self)
        menu.addAction(self._menus["file"]["connect"])
        menu.addAction(self._menus["file"]["disconnect"])
        menu.addSeparator()
        menu.addAction(self._menus["file"]["exit"])

        self._trayIcon = Systray(self._icon, self)
        self._trayIcon.setContextMenu(menu)
        self._trayIcon.setToolTip(self.DESCRIPTION)

    def _addMenu(self):
        self._menus = {
            "file": {
                "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"),
                "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"),
                "exit": self._createAction(self._("E&xit"), self.exit)
            },
            "settings": {
                "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"),
                "options": self._createAction(self._("&Options..."), self.options)
            },
            "help": {
                "about": self._createAction(self._("A&bout"), self.about)
            }
        }

        menu = self.menuBar()

        file_menu = menu.addMenu(self._("&File"))
        file_menu.addAction(self._menus["file"]["connect"])
        file_menu.addAction(self._menus["file"]["disconnect"])
        file_menu.addSeparator()
        file_menu.addAction(self._menus["file"]["exit"])

        settings_menu = menu.addMenu(self._("S&ettings"))
        settings_menu.addAction(self._menus["settings"]["alerts"])
        settings_menu.addSeparator()
        settings_menu.addAction(self._menus["settings"]["options"])

        help_menu = menu.addMenu(self._("&Help"))
        help_menu.addAction(self._menus["help"]["about"])

    def _addToolbar(self):
        self._toolBar = {
            "connect": self._menus["file"]["connect"],
            "disconnect": self._menus["file"]["disconnect"],
            "roomsLabel": QtGui.QLabel(self._("Rooms:")),
            "rooms": QtGui.QComboBox(),
            "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"),
            "alerts": self._menus["settings"]["alerts"]
        }

        self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected)

        toolBar = self.toolBar()
        toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        toolBar.addAction(self._toolBar["connect"])
        toolBar.addAction(self._toolBar["disconnect"])
        toolBar.addSeparator();
        toolBar.addWidget(self._toolBar["roomsLabel"])
        toolBar.addWidget(self._toolBar["rooms"])
        toolBar.addAction(self._toolBar["join"])
        toolBar.addSeparator();
        toolBar.addAction(self._toolBar["alerts"])

    def _createAction(self, text, slot=None, shortcut=None, icon=None,
        tip=None, checkable=False, signal="triggered()"):
        """ Create an action """
        action = QtGui.QAction(text, self)
        if icon is not None:
            if not isinstance(icon, QtGui.QIcon):
                action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon)))
            else:
                action.setIcon(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, QtCore.SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def _(self, string, module=None):
        return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))
Example #15
0
class Snakefire(object):
    DOMAIN = "snakefire.org"
    NAME = "Snakefire"
    DESCRIPTION = "Snakefire: Campfire Linux Client"
    VERSION = "1.0.1"
    ICON = "snakefire.png"
    COLORS = {
        "time": "c0c0c0",
        "alert": "ff0000",
        "join": "cb81cb",
        "leave": "cb81cb",
        "topic": "808080",
        "upload": "000000",
        "message": "000000",
        "nick": "808080",
        "nickAlert": "ff0000",
        "nickSelf": "000080",
        "tabs": {
            "normal": None,
            "new": QtGui.QColor(0, 0, 255),
            "alert": QtGui.QColor(255, 0, 0)
        }
    }

    def __init__(self):
        self.DESCRIPTION = self._(self.DESCRIPTION)
        self._worker = None
        self._settings = {}
        self._canConnect = False
        self._cfDisconnected()
        self._qsettings = QtCore.QSettings()
        self._icon = QtGui.QIcon(":/icons/%s" % (self.ICON))
        self.setWindowIcon(self._icon)
        self.setAcceptDrops(True)
        self._setupUI()

        settings = self.getSettings("connection")

        self._canConnect = False
        if settings["subdomain"] and settings["user"] and settings["password"]:
            self._canConnect = True

        self._updateLayout()

        if settings["connect"]:
            self.connectNow()

    def _(self, string, module=None):
        return str(
            QtCore.QCoreApplication.translate(module or Snakefire.NAME,
                                              string))

    def showEvent(self, event):
        if self._trayIcon.isVisible():
            if self._trayIcon.isAlerting():
                self._trayIcon.stopAlert()
            return
        self._trayIcon.show()

    def dragEnterEvent(self, event):
        room = self.getCurrentRoom()
        canUpload = not self._rooms[room.id]["upload"] if room else False
        if canUpload and self._getDropFile(event):
            event.acceptProposedAction()

    def dropEvent(self, event):
        room = self.getCurrentRoom()
        path = self._getDropFile(event)
        if room and path:
            self._upload(room, path)

    def _getDropFile(self, event):
        files = []
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                path = url.path()
                if path and os.path.exists(path) and os.path.isfile(path):
                    try:
                        handle = open(str(path))
                        handle.close()
                        files.append(str(path))
                    except Exception as e:
                        pass
            if len(files) > 1:
                files = []
        return files[0] if files else None

    def getSetting(self, group, setting):
        settings = self.getSettings(group, asString=False)
        return settings[setting] if setting in settings else None

    def setSetting(self, group, setting, value):
        self._qsettings.beginGroup(group)
        self._qsettings.setValue(setting, value)
        self._qsettings.endGroup()

    def getSettings(self, group, asString=True, reload=False):
        defaults = {
            "connection": {
                "subdomain": None,
                "user": None,
                "password": None,
                "ssl": False,
                "connect": False,
                "join": False,
                "rooms": []
            },
            "program": {
                "minimize": False
            }
        }

        if reload or not group in self._settings:
            settings = defaults[group] if group in defaults else {}

            self._qsettings.beginGroup(group)
            for setting in self._qsettings.childKeys():
                settings[str(setting)] = self._qsettings.value(
                    setting).toPyObject()
            self._qsettings.endGroup()

            boolSettings = []
            if group == "connection":
                boolSettings += ["ssl", "connect", "join"]
            elif group == "program":
                boolSettings += ["minimize"]

            for boolSetting in boolSettings:
                try:
                    settings[boolSetting] = True if ["true", "1"].index(
                        str(settings[boolSetting]).lower()) >= 0 else False
                except:
                    settings[boolSetting] = False

            if group == "connection" and settings["subdomain"] and settings[
                    "user"]:
                settings["password"] = keyring.get_password(
                    self.NAME,
                    str(settings["subdomain"]) + "_" + str(settings["user"]))

            self._settings[group] = settings

        settings = self._settings[group]
        if asString:
            for setting in settings:
                if not isinstance(settings[setting], bool):
                    settings[setting] = str(
                        settings[setting]) if settings[setting] else ""

        return settings

    def setSettings(self, group, settings):
        self._settings[group] = settings

        self._qsettings.beginGroup(group)
        for setting in self._settings[group]:
            if group != "connection" or setting != "password":
                self._qsettings.setValue(setting, settings[setting])
            elif settings["subdomain"] and settings["user"]:
                keyring.set_password(
                    self.NAME, settings["subdomain"] + "_" + settings["user"],
                    settings[setting])
        self._qsettings.endGroup()

        if group == "connection":
            self._canConnect = False
            if settings["subdomain"] and settings["user"] and settings[
                    "password"]:
                self._canConnect = True
            self._updateLayout()

    def exit(self):
        self._forceClose = True
        self.close()

    def changeEvent(self, event):
        if self.getSetting("program", "minimize") and event.type(
        ) == QtCore.QEvent.WindowStateChange and self.isMinimized():
            self.hide()
            event.ignore()
        else:
            event.accept()

    def closeEvent(self, event):
        if (not hasattr(self, "_forceClose")
                or not self._forceClose) and self.getSetting(
                    "program", "minimize"):
            self.hide()
            event.ignore()
        else:
            if self.getSetting("connection", "join"):
                self.setSetting(
                    "connection", "rooms",
                    ",".join([str(roomId) for roomId in self._rooms.keys()]))

            self.disconnectNow()

            if hasattr(self, "_workers") and self._workers:
                for worker in self._workers:
                    worker.terminate()
                    worker.wait()

            if hasattr(self, "_worker") and self._worker:
                self._worker.terminate()
                self._worker.wait()

            self.setSetting("window", "size", self.size())
            self.setSetting("window", "position", self.pos())

            event.accept()

    def alerts(self):
        dialog = AlertsDialog(self)
        dialog.open()

    def options(self):
        dialog = OptionsDialog(self)
        dialog.open()

    def connectNow(self):
        if not self._canConnect:
            return

        self._connecting = True
        self.statusBar().showMessage(self._("Connecting with Campfire..."))
        self._updateLayout()

        settings = self.getSettings("connection")

        self._worker = CampfireWorker(settings["subdomain"], settings["user"],
                                      settings["password"], settings["ssl"],
                                      self)
        self._connectWorkerSignals(self._worker)
        self._worker.connect()

    def disconnectNow(self):
        self.statusBar().showMessage(self._("Disconnecting from Campfire..."))
        if self._worker and hasattr(self, "_rooms"):
            # Using keys() since the dict could be changed (by _cfRoomLeft())
            # while iterating on it
            for roomId in self._rooms.keys():
                if roomId in self._rooms and self._rooms[roomId]["room"]:
                    self._worker.leave(self._rooms[roomId]["room"], False)

        self._cfDisconnected()
        self._updateLayout()

    def joinRoom(self, roomIndex=None):
        room = self._roomInIndex(
            roomIndex if roomIndex else self._toolBar["rooms"].currentIndex())
        if not room:
            return

        self._toolBar["join"].setEnabled(False)
        self.statusBar().showMessage(
            self._("Joining room %s...") % room["name"])

        self._rooms[room["id"]] = {
            "room": None,
            "stream": None,
            "upload": None,
            "tab": None,
            "editor": None,
            "usersList": None,
            "topicLabel": None,
            "filesLabel": None,
            "uploadButton": None,
            "uploadLabel": None,
            "uploadWidget": None,
            "newMessages": 0
        }
        self._getWorker().join(room["id"])

    def speak(self):
        message = self._editor.document().toPlainText()
        room = self.getCurrentRoom()
        if not room or message.trimmed().isEmpty():
            return

        self.statusBar().showMessage(
            self._("Sending message to %s...") % room.name)
        self._getWorker().speak(room, unicode(message))
        self._editor.document().clear()

    def uploadFile(self):
        room = self.getCurrentRoom()
        if not room:
            return

        path = QtGui.QFileDialog.getOpenFileName(
            self, self._("Select file to upload"))
        if path:
            self._upload(room, str(path))

    def uploadCancel(self):
        room = self.getCurrentRoom()
        if not room:
            return

        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()
            self._rooms[room.id]["upload"] = None

        self._rooms[room.id]["uploadWidget"].hide()

    def leaveRoom(self, roomId):
        if roomId in self._rooms:
            self.statusBar().showMessage(
                self._("Leaving room %s...") %
                self._rooms[roomId]["room"].name)
            self._getWorker().leave(self._rooms[roomId]["room"])

    def changeTopic(self):
        room = self.getCurrentRoom()
        if not room:
            return
        topic, ok = QtGui.QInputDialog.getText(
            self, self._("Change topic"),
            self._("Enter new topic for room %s") % room.name,
            QtGui.QLineEdit.Normal, room.topic)
        if ok:
            self.statusBar().showMessage(
                self._("Changing topic for room %s...") % room.name)
            self._getWorker().changeTopic(room, topic)

    def updateRoomUsers(self, roomId=None):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            self.statusBar().showMessage(
                self._("Getting users in %s...") %
                self._rooms[roomId]["room"].name)
            self._getWorker().users(self._rooms[roomId]["room"])

    def updateRoomUploads(self, roomId=None):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            self.statusBar().showMessage(
                self._("Getting uploads in %s...") %
                self._rooms[roomId]["room"].name)
            self._getWorker().uploads(self._rooms[roomId]["room"])

    def getCurrentRoom(self):
        index = self._tabs.currentIndex()
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["tab"] == index:
                return self._rooms[roomId]["room"]

    def _cfStreamMessage(self, room, message, live=True, updateRoom=True):
        if (not message.user or
            (live and message.is_text() and message.is_by_current_user())
                or not room.id in self._rooms):
            return

        user = message.user.name
        notify = True
        alert = False

        if message.is_text() and not message.is_by_current_user():
            alert = self._matchesAlert(message.body)

        html = None
        if message.is_joining():
            html = "<font color=\"#%s\">" % self.COLORS["join"]
            html += "--&gt; %s joined %s" % (user, room.name)
            html += "</font>"
        elif message.is_leaving():
            html = "<font color=\"#%s\">" % self.COLORS["leave"]
            html += "&lt;-- %s has left %s" % (user, room.name)
            html += "</font>"
        elif message.is_text():
            body = self._plainTextToHTML(
                message.tweet["tweet"] if message.is_tweet() else message.body)
            if message.is_tweet():
                body = "<a href=\"%s\">%s</a> <a href=\"%s\">tweeted</a>: %s" % (
                    "http://twitter.com/%s" % message.tweet["user"],
                    message.tweet["user"], message.tweet["url"], body)
            elif message.is_paste():
                body = "<br /><hr /><code>%s</code><hr />" % body
            else:
                body = self._autoLink(body)

            created = QtCore.QDateTime(message.created_at.year,
                                       message.created_at.month,
                                       message.created_at.day,
                                       message.created_at.hour,
                                       message.created_at.minute,
                                       message.created_at.second)
            created.setTimeSpec(QtCore.Qt.UTC)

            createdFormat = "h:mm ap"
            if created.daysTo(QtCore.QDateTime.currentDateTime()):
                createdFormat = "MMM d,  %s" % createdFormat

            html = "<font color=\"#%s\">[%s]</font> " % (
                self.COLORS["time"],
                created.toLocalTime().toString(createdFormat))

            if alert:
                html += "<font color=\"#%s\">" % self.COLORS["alert"]
            else:
                html += "<font color=\"#%s\">" % self.COLORS["message"]

            if message.is_by_current_user():
                html += "<font color=\"#%s\">" % self.COLORS["nickSelf"]
            elif alert:
                html += "<font color=\"#%s\">" % self.COLORS["nickAlert"]
            else:
                html += "<font color=\"#%s\">" % self.COLORS["nick"]

            html += "%s" % ("<strong>%s</strong>" % user if alert else user)
            html += "</font>: "
            html += body
            html += "</font>"
        elif message.is_upload():
            html = "<font color=\"#%s\">" % self.COLORS["upload"]
            html += "<strong>%s</strong> uploaded <a href=\"%s\">%s</a>" % (
                user, message.upload["url"], message.upload["name"])
            html += "</font>"
        elif message.is_topic_change():
            html = "<font color=\"#%s\">" % self.COLORS["leave"]
            html += "%s changed topic to <strong>%s</strong>" % (user,
                                                                 message.body)
            html += "</font>"

        if html:
            html = "%s<br />" % html
            editor = self._rooms[room.id]["editor"]
            if not editor:
                return

            scrollbar = editor.verticalScrollBar()
            currentScrollbarValue = scrollbar.value()
            autoScroll = (currentScrollbarValue == scrollbar.maximum())
            editor.moveCursor(QtGui.QTextCursor.End)
            editor.textCursor().insertHtml(html)
            if autoScroll:
                scrollbar.setValue(scrollbar.maximum())
            else:
                scrollbar.setValue(currentScrollbarValue)

            tabIndex = self._rooms[room.id]["tab"]
            tabBar = self._tabs.tabBar()
            isActiveTab = (self.isActiveWindow()
                           and tabIndex == self._tabs.currentIndex())

            if message.is_text() and not isActiveTab:
                self._rooms[room.id]["newMessages"] += 1

            if self._rooms[room.id]["newMessages"] > 0:
                tabBar.setTabText(
                    tabIndex, "%s (%s)" %
                    (room.name, self._rooms[room.id]["newMessages"]))

            if not isActiveTab and (
                    alert or self._rooms[room.id]["newMessages"] > 0
            ) and tabBar.tabTextColor(
                    tabIndex) == self.COLORS["tabs"]["normal"]:
                tabBar.setTabTextColor(
                    tabIndex, self.COLORS["tabs"]["alert" if alert else "new"])

            if alert:
                if not isActiveTab:
                    self._trayIcon.alert()
                if notify:
                    self._notify(room, message.body)

        if updateRoom:
            if (message.is_joining() or message.is_leaving()):
                self.updateRoomUsers(room.id)
            elif message.is_upload():
                self.updateRoomUploads(room.id)
            elif message.is_topic_change(
            ) and not message.is_by_current_user():
                self._cfTopicChanged(room, message.body)

    def _matchesAlert(self, message):
        matches = False
        regexes = []
        words = ["Mariano Iglesias", "Mariano"]
        for word in words:
            regexes.append("\\b%s\\b" % word)

        for regex in regexes:
            if QtCore.QString(message).contains(
                    QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)):
                matches = True
                break
        return matches

    def _cfConnected(self, user, rooms):
        self._connecting = False
        self._connected = True
        self._rooms = {}

        self._toolBar["rooms"].clear()
        for room in rooms:
            self._toolBar["rooms"].addItem(room["name"], room)

        self.statusBar().showMessage(
            self._("%s connected to Campfire") % user.name, 5000)
        self._updateLayout()

        if self.getSetting("connection", "join"):
            rooms = self.getSetting("connection", "rooms")
            if rooms:
                for roomId in rooms.split(","):
                    count = self._toolBar["rooms"].count()
                    if count:
                        roomIndex = None
                        for i in range(count):
                            data = self._toolBar["rooms"].itemData(i)
                            if not data.isNull():
                                data = data.toMap()
                                for key in data:
                                    if str(key) == "id" and str(
                                            data[key].toString()) == roomId:
                                        roomIndex = i
                                        break
                                if roomIndex is not None:
                                    break
                        if roomIndex is not None:
                            self.joinRoom(roomIndex)

    def _cfDisconnected(self):
        self._connecting = False
        self._connected = False
        self._rooms = {}
        self._worker = None
        self.statusBar().clearMessage()

    def _cfRoomJoined(self, room, messages=[]):
        if room.id not in self._rooms:
            return
        self._rooms[room.id].update(self._setupRoomUI(room))
        self._rooms[room.id]["room"] = room
        self._rooms[room.id]["stream"] = self._worker.getStream(room)
        self.updateRoomUsers(room.id)
        self.updateRoomUploads(room.id)
        self.statusBar().showMessage(
            self._("Joined room %s") % room.name, 5000)
        self._updatedRoomsList()
        if messages:
            for message in messages:
                self._cfStreamMessage(room,
                                      message,
                                      live=False,
                                      updateRoom=False)

    def _cfSpoke(self, room, message):
        self._cfStreamMessage(room, message, live=False)
        self.statusBar().clearMessage()

    def _cfRoomLeft(self, room):
        if self._rooms[room.id]["stream"]:
            self._rooms[room.id]["stream"].stop().join()
        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()

        self._tabs.removeTab(self._rooms[room.id]["tab"])
        del self._rooms[room.id]
        self.statusBar().showMessage(self._("Left room %s") % room.name, 5000)
        self._updatedRoomsList()

    def _cfRoomUsers(self, room, users):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        self.statusBar().clearMessage()
        self._rooms[room.id]["usersList"].clear()
        for user in users:
            item = QtGui.QListWidgetItem(user["name"])
            item.setData(QtCore.Qt.UserRole, user)
            self._rooms[room.id]["usersList"].addItem(item)

    def _cfRoomUploads(self, room, uploads):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        self.statusBar().clearMessage()
        label = self._rooms[room.id]["filesLabel"]
        if uploads:
            html = ""
            for upload in uploads:
                html += "%s&bull; <a href=\"%s\">%s</a>" % (
                    "<br />" if html else "", upload["full_url"],
                    upload["name"])
            html = "%s<br />%s" % (self._("Latest uploads:"), html)

            label.setText(html)
            if not label.isVisible():
                label.show()
        elif label.isVisible():
            label.setText("")
            label.hide()

    def _cfUploadProgress(self, room, current, total):
        if not room.id in self._rooms:
            return

        progressBar = self._rooms[room.id]["uploadProgressBar"]
        if not self._rooms[room.id]["uploadWidget"].isVisible():
            self._rooms[room.id]["uploadWidget"].show()
            progressBar.setMaximum(total)

        progressBar.setValue(current)

    def _cfUploadFinished(self, room):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["upload"].join()
        self._rooms[room.id]["upload"] = None
        self._rooms[room.id]["uploadWidget"].hide()

    def _cfTopicChanged(self, room, topic):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["topicLabel"].setText(topic)
        self.statusBar().clearMessage()

    def _cfConnectError(self, error):
        self._cfDisconnected()
        self._updateLayout()
        self._cfError(error)

    def _cfError(self, error):
        self.statusBar().clearMessage()
        QtGui.QMessageBox.critical(self, "Error", str(error))

    def _roomSelected(self, index):
        self._updatedRoomsList(index)

    def _upload(self, room, path):
        self._rooms[room.id]["upload"] = self._worker.upload(room, path)
        self._updateRoomLayout()

    def _roomTabClose(self, tabIndex):
        for roomId in self._rooms:
            if self._rooms[roomId]["tab"] == tabIndex:
                self.leaveRoom(roomId)
                break

    def _roomTabFocused(self):
        tabIndex = self._tabs.currentIndex()
        if tabIndex < 0 or not self.isActiveWindow():
            return

        room = self._roomInTabIndex(tabIndex)
        if not room:
            return

        tabBar = self._tabs.tabBar()

        if self._rooms[room.id]["newMessages"] > 0:
            self._rooms[room.id]["newMessages"] = 0
            tabBar.setTabText(tabIndex, room.name)

        if tabBar.tabTextColor(tabIndex) != self.COLORS["tabs"]["normal"]:
            tabBar.setTabTextColor(tabIndex, self.COLORS["tabs"]["normal"])

        self._updateRoomLayout()

    def _roomInTabIndex(self, index):
        room = None
        for key in self._rooms:
            if self._rooms[key]["tab"] == index:
                room = self._rooms[key]["room"]
                break
        return room

    def _roomInIndex(self, index):
        room = {}
        data = self._toolBar["rooms"].itemData(index)
        if not data.isNull():
            data = data.toMap()
            for key in data:
                room[str(key)] = str(unicode(data[key].toString(), "utf-8"))
        return room

    def _connectWorkerSignals(self, worker):
        self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"),
                     self._cfError)
        self.connect(worker,
                     QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfConnected)
        self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"),
                     self._cfConnectError)
        self.connect(worker,
                     QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfRoomJoined)
        self.connect(worker,
                     QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfSpoke)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfStreamMessage)
        self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"),
                     self._cfRoomLeft)
        self.connect(worker,
                     QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfRoomUsers)
        self.connect(worker,
                     QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfRoomUploads)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfUploadProgress)
        self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"),
                     self._cfUploadFinished)
        self.connect(
            worker,
            QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"),
            self._cfTopicChanged)

    def _getWorker(self):
        if not hasattr(self, "_workers"):
            self._workers = []

        if self._workers:
            for worker in self._workers:
                if worker.isFinished():
                    return worker

        worker = copy.copy(self._worker)
        self._connectWorkerSignals(worker)
        self._workers.append(worker)
        return worker

    def _updatedRoomsList(self, index=None):
        if not index:
            index = self._toolBar["rooms"].currentIndex()

        room = self._roomInIndex(index)

        self._toolBar["join"].setEnabled(False)
        if not room or room["id"] not in self._rooms:
            self._toolBar["join"].setEnabled(True)

        centralWidget = self.centralWidget()
        if not self._tabs.count():
            centralWidget.hide()
        else:
            centralWidget.show()

    def _notify(self, room, message):
        raise NotImplementedError("_notify() must be implemented")

    def _updateRoomLayout(self):
        room = self.getCurrentRoom()
        if room:
            canUpload = not self._rooms[room.id]["upload"]
            uploadButton = self._rooms[room.id]["uploadButton"]
            if ((canUpload and not uploadButton.isEnabled())
                    or (not canUpload and uploadButton.isEnabled())):
                uploadButton.setEnabled(canUpload)

    def _updateLayout(self):
        self._menus["file"]["connect"].setEnabled(not self._connected
                                                  and self._canConnect
                                                  and not self._connecting)
        self._menus["file"]["disconnect"].setEnabled(self._connected)

        roomsEmpty = self._toolBar["rooms"].count(
        ) == 1 and self._toolBar["rooms"].itemData(0).isNull()
        if not roomsEmpty and (not self._connected
                               or not self._toolBar["rooms"].count()):
            self._toolBar["rooms"].clear()
            self._toolBar["rooms"].addItem(self._("No rooms available"))
            self._toolBar["rooms"].setEnabled(False)
        elif not roomsEmpty:
            self._toolBar["rooms"].setEnabled(True)

        self._toolBar["roomsLabel"].setEnabled(
            self._toolBar["rooms"].isEnabled())
        self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled())

    def _setupRoomUI(self, room):
        topicLabel = ClickableQLabel(room.topic)
        topicLabel.setToolTip(self._("Click here to change room's topic"))
        topicLabel.setWordWrap(True)
        self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic)

        editor = QtGui.QTextBrowser()
        editor.setOpenExternalLinks(True)

        usersList = QtGui.QListWidget()

        filesLabel = QtGui.QLabel("")
        filesLabel.setOpenExternalLinks(True)
        filesLabel.setWordWrap(True)
        filesLabel.hide()

        uploadButton = QtGui.QPushButton(self._("&Upload new file"))
        self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile)

        uploadProgressBar = QtGui.QProgressBar()
        uploadProgressLabel = QtGui.QLabel(self._("Uploading:"))

        uploadCancelButton = QtGui.QPushButton(self._("Cancel"))
        self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"),
                     self.uploadCancel)

        uploadLayout = QtGui.QHBoxLayout()
        uploadLayout.addWidget(uploadProgressLabel)
        uploadLayout.addWidget(uploadProgressBar)
        uploadLayout.addWidget(uploadCancelButton)

        uploadWidget = QtGui.QWidget()
        uploadWidget.setLayout(uploadLayout)
        uploadWidget.hide()

        leftFrameLayout = QtGui.QVBoxLayout()
        leftFrameLayout.addWidget(topicLabel)
        leftFrameLayout.addWidget(editor)
        leftFrameLayout.addWidget(uploadWidget)

        rightFrameLayout = QtGui.QVBoxLayout()
        rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:")))
        rightFrameLayout.addWidget(usersList)
        rightFrameLayout.addWidget(filesLabel)
        rightFrameLayout.addWidget(uploadButton)
        rightFrameLayout.addStretch(1)

        leftFrame = QtGui.QWidget()
        leftFrame.setLayout(leftFrameLayout)

        rightFrame = QtGui.QWidget()
        rightFrame.setLayout(rightFrameLayout)

        splitter = QtGui.QSplitter()
        splitter.addWidget(leftFrame)
        splitter.addWidget(rightFrame)
        splitter.setSizes(
            [splitter.size().width() * 0.75,
             splitter.size().width() * 0.25])

        index = self._tabs.addTab(splitter, room.name)
        self._tabs.setCurrentIndex(index)

        if not self.COLORS["tabs"]["normal"]:
            self.COLORS["tabs"]["normal"] = self._tabs.tabBar().tabTextColor(
                index)
        else:
            self._tabs.tabBar().setTabTextColor(index,
                                                self.COLORS["tabs"]["normal"])

        return {
            "tab": index,
            "editor": editor,
            "usersList": usersList,
            "topicLabel": topicLabel,
            "filesLabel": filesLabel,
            "uploadButton": uploadButton,
            "uploadWidget": uploadWidget,
            "uploadProgressBar": uploadProgressBar,
            "uploadProgressLabel": uploadProgressLabel
        }

    def _setupUI(self):
        self.setWindowTitle(self.NAME)

        self._addMenu()
        self._addToolbar()

        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"),
                     self._roomTabFocused)
        self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"),
                     self._roomTabClose)

        self._editor = QtGui.QPlainTextEdit()
        self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2)
        self._editor.installEventFilter(
            SuggesterKeyPressEventFilter(self, Suggester(self._editor)))

        speakButton = QtGui.QPushButton(self._("&Send"))
        self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

        grid = QtGui.QGridLayout()
        grid.setRowStretch(0, 1)
        grid.addWidget(self._tabs, 0, 0, 1, -1)
        grid.addWidget(self._editor, 1, 0)
        grid.addWidget(speakButton, 1, 1)

        widget = QtGui.QWidget()
        widget.setLayout(grid)
        self.setCentralWidget(widget)

        tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
        self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"),
                     self._roomTabFocused)
        widget.installEventFilter(tabWidgetFocusEventFilter)

        self.centralWidget().hide()

        size = self.getSetting("window", "size")

        if not size:
            size = QtCore.QSize(640, 480)

        self.resize(size)

        position = self.getSetting("window", "position")
        if not position:
            screen = QtGui.QDesktopWidget().screenGeometry()
            position = QtCore.QPoint((screen.width() - size.width()) / 2,
                                     (screen.height() - size.height()) / 2)

        self.move(position)

        self._updateLayout()

        menu = QtGui.QMenu(self)
        menu.addAction(self._menus["file"]["connect"])
        menu.addAction(self._menus["file"]["disconnect"])
        menu.addSeparator()
        menu.addAction(self._menus["file"]["exit"])

        self._trayIcon = Systray(self._icon, self)
        self._trayIcon.setContextMenu(menu)
        self._trayIcon.setToolTip(self.DESCRIPTION)

    def _addMenu(self):
        self._menus = {
            "file": {
                "connect":
                self._createAction(self._("&Connect"),
                                   self.connectNow,
                                   icon="connect.png"),
                "disconnect":
                self._createAction(self._("&Disconnect"),
                                   self.disconnectNow,
                                   icon="disconnect.png"),
                "exit":
                self._createAction(self._("E&xit"), self.exit)
            },
            "settings": {
                "alerts":
                self._createAction(self._("&Alerts..."),
                                   self.alerts,
                                   icon="alerts.png"),
                "options":
                self._createAction(self._("&Options..."), self.options)
            },
            "help": {
                "about": self._createAction(self._("A&bout"))
            }
        }

        menu = self.menuBar()

        file_menu = menu.addMenu(self._("&File"))
        file_menu.addAction(self._menus["file"]["connect"])
        file_menu.addAction(self._menus["file"]["disconnect"])
        file_menu.addSeparator()
        file_menu.addAction(self._menus["file"]["exit"])

        settings_menu = menu.addMenu(self._("S&ettings"))
        settings_menu.addAction(self._menus["settings"]["alerts"])
        settings_menu.addSeparator()
        settings_menu.addAction(self._menus["settings"]["options"])

        help_menu = menu.addMenu(self._("&Help"))
        help_menu.addAction(self._menus["help"]["about"])

    def _addToolbar(self):
        self._toolBar = {
            "connect":
            self._menus["file"]["connect"],
            "disconnect":
            self._menus["file"]["disconnect"],
            "roomsLabel":
            QtGui.QLabel(self._("Rooms:")),
            "rooms":
            QtGui.QComboBox(),
            "join":
            self._createAction(self._("Join room"),
                               self.joinRoom,
                               icon="join.png"),
            "alerts":
            self._menus["settings"]["alerts"]
        }

        self.connect(self._toolBar["rooms"],
                     QtCore.SIGNAL("currentIndexChanged(int)"),
                     self._roomSelected)

        toolBar = self.toolBar()
        toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        toolBar.addAction(self._toolBar["connect"])
        toolBar.addAction(self._toolBar["disconnect"])
        toolBar.addSeparator()
        toolBar.addWidget(self._toolBar["roomsLabel"])
        toolBar.addWidget(self._toolBar["rooms"])
        toolBar.addAction(self._toolBar["join"])
        toolBar.addSeparator()
        toolBar.addAction(self._toolBar["alerts"])

    def _createAction(self,
                      text,
                      slot=None,
                      shortcut=None,
                      icon=None,
                      tip=None,
                      checkable=False,
                      signal="triggered()"):
        """ Create an action """
        action = QtGui.QAction(text, self)
        if icon is not None:
            if not isinstance(icon, QtGui.QIcon):
                action.setIcon(QtGui.QIcon(":/icons/%s" % (icon)))
            else:
                action.setIcon(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, QtCore.SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def _plainTextToHTML(self, string):
        return string.replace("<",
                              "&lt;").replace(">",
                                              "&gt;").replace("\n", "<br />")

    def _autoLink(self, string):
        urlre = re.compile(
            "(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?"
        )
        urls = urlre.findall(string)
        cleanUrls = []
        for url in urls:
            if url[1]:
                continue

            currentUrl = url[0]
            if currentUrl[0] == '(' and currentUrl[-1] == ')':
                currentUrl = currentUrl[1:-1]

            if currentUrl in cleanUrls:
                continue

            cleanUrls.append(currentUrl)
            string = re.sub(
                "(?<!(=\"|\">))" + re.escape(currentUrl),
                "<a href=\"" + currentUrl + "\">" + currentUrl + "</a>",
                string)
        return string
Example #16
0
class ScudCloud(QtGui.QMainWindow):

    plugins = True
    debug = False
    forceClose = False
    messages = 0
    speller = Speller()

    def __init__(self, parent=None, settings_path=""):
        super(ScudCloud, self).__init__(parent)
        self.setWindowTitle("ScudCloud")
        self.settings_path = settings_path
        self.notifier = Notifier(Resources.APP_NAME, Resources.get_path("scudcloud.png"))
        self.settings = QSettings(self.settings_path + "/scudcloud.cfg", QSettings.IniFormat)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.leftPane = LeftPane(self)
        self.stackedWidget = QtGui.QStackedWidget()
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.startURL = Resources.SIGNIN_URL
        if self.identifier is not None:
            self.startURL = self.domain()
        self.addWrapper(self.startURL)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(ScudCloud.minimized)
        self.installEventFilter(self)
        self.statusBar().showMessage("Loading Slack...")

    def addWrapper(self, url):
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        webView.page().networkAccessManager().setCache(self.diskCache)
        webView.load(QtCore.QUrl(url))
        webView.show()
        self.stackedWidget.addWidget(webView)
        self.stackedWidget.setCurrentWidget(webView)

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # Required by Youtube videos (HTML5 video support only on Qt5)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins)
        # We don't want Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False)
        # Enabling Local Storage (now required by Slack)
        QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True)
        # We need browsing history (required to not limit LocalStorage)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False)
        # Enabling Cache
        self.diskCache = QNetworkDiskCache(self)
        self.diskCache.setCacheDirectory(self.settings_path)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def toggleMenuBar(self):
        menu = self.menuBar()
        state = menu.isHidden()
        menu.setVisible(state)
        if state:
            self.settings.setValue("Menu", "False")
        else:
            self.settings.setValue("Menu", "True")

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.setWindowState(QtCore.Qt.WindowMaximized)

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addMenu(self):
        self.menus = {
            "file": {
                "preferences": self.createAction("Preferences", lambda: self.current().preferences()),
                "systray": self.createAction("Close to Tray", self.systray, None, True),
                "addTeam": self.createAction("Sign in to Another Team", lambda: self.switchTo(Resources.SIGNIN_URL)),
                "signout": self.createAction("Signout", lambda: self.current().logout()),
                "close": self.createAction("Close", self.close, QKeySequence.Close),
                "exit": self.createAction("Quit", self.exit, QKeySequence.Quit),
            },
            "edit": {},
            "view": {
                "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut),
                "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11),
                "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12),
            },
            "help": {
                "help": self.createAction(
                    "Help and Feedback", lambda: self.current().help(), QKeySequence.HelpContents
                ),
                "center": self.createAction("Slack Help Center", lambda: self.current().helpCenter()),
                "about": self.createAction("About", lambda: self.current().about()),
            },
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        self.editMenu = menu.addMenu("&Edit")
        self.updateEditMenu()
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        if Unity is None:
            viewMenu.addAction(self.menus["view"]["hidemenu"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)
        # Restore menu visibility
        visible = self.settings.value("Menu")
        if visible is not None and visible == "False":
            menu.setVisible(False)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def updateEditMenu(self):
        self.editMenu.clear()
        self.menus["edit"] = {
            "undo": self.current().pageAction(QtWebKit.QWebPage.Undo),
            "redo": self.current().pageAction(QtWebKit.QWebPage.Redo),
            "cut": self.current().pageAction(QtWebKit.QWebPage.Cut),
            "copy": self.current().pageAction(QtWebKit.QWebPage.Copy),
            "paste": self.current().pageAction(QtWebKit.QWebPage.Paste),
            "back": self.current().pageAction(QtWebKit.QWebPage.Back),
            "forward": self.current().pageAction(QtWebKit.QWebPage.Forward),
            "reload": self.current().pageAction(QtWebKit.QWebPage.Reload),
        }
        self.editMenu.addAction(self.menus["edit"]["undo"])
        self.editMenu.addAction(self.menus["edit"]["redo"])
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.menus["edit"]["cut"])
        self.editMenu.addAction(self.menus["edit"]["copy"])
        self.editMenu.addAction(self.menus["edit"]["paste"])
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.menus["edit"]["back"])
        self.editMenu.addAction(self.menus["edit"]["forward"])
        self.editMenu.addAction(self.menus["edit"]["reload"])

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        action.triggered.connect(slot)
        if shortcut is not None:
            action.setShortcut(shortcut)
            self.addAction(action)
        if checkable:
            action.setCheckable(True)
        return action

    def domain(self):
        if self.identifier.endswith(".slack.com"):
            return self.identifier
        else:
            return "https://" + self.identifier + ".slack.com"

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        for t in teams:
            # If team_icon is not present, it's because team is already connected
            if "team_icon" in t:
                self.leftPane.addTeam(t["id"], t["team_name"], t["team_url"], t["team_icon"]["image_44"], t == teams[0])
        if len(teams) > 1:
            self.leftPane.show()

    def switchTo(self, url):
        exists = False
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                self.stackedWidget.setCurrentIndex(i)
                self.quicklist(self.current().listChannels())
                self.current().setFocus()
                self.leftPane.click(i)
                exists = True
                break
        if not exists:
            self.addWrapper(url)
        self.updateEditMenu()

    def eventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            modifiers = QtGui.QApplication.keyboardModifiers()
            if modifiers == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1:
                    self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2:
                    self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3:
                    self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4:
                    self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5:
                    self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6:
                    self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7:
                    self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8:
                    self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9:
                    self.leftPane.click(8)
                # Ctrl + Tab
                elif event.key() == QtCore.Qt.Key_Tab:
                    self.leftPane.clickNext(1)
            # Ctrl + BackTab
            if (modifiers & QtCore.Qt.ControlModifier) and (modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_Backtab:
                    self.leftPane.clickNext(-1)
            # Ctrl + Shift + <key>
            if (modifiers & QtCore.Qt.ShiftModifier) and (modifiers & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V:
                    self.current().createSnippet()
        return QtGui.QMainWindow.eventFilter(self, obj, event)

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())
            # Let's save the first team registered as default
            qUrl = self.stackedWidget.widget(0).url()
            if self.identifier is None and Resources.MESSAGES_URL_RE.match(qUrl.toString()):
                self.settings.setValue("Domain", "https://" + qUrl.host())

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.forceClose = True
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            if channels is not None:
                ql = Dbusmenu.Menuitem.new()
                self.launcher.set_property("quicklist", ql)
                for c in channels:
                    if c["is_member"]:
                        item = Dbusmenu.Menuitem.new()
                        item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c["name"])
                        item.property_set("id", c["name"])
                        item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message, icon=None):
        self.notifier.notify(title, message, icon)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()

    def count(self):
        total = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            messages = widget.highlights
            if messages == 0:
                self.leftPane.stopAlert(widget.team())
            else:
                self.leftPane.alert(widget.team())
            if messages is not None:
                total += messages
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
        self.messages = total
Example #17
0
class Snakefire(object):
    DOMAIN = "www.snakefire.org"
    NAME = "Snakefire"
    DESCRIPTION = "Snakefire: Campfire Linux Client"
    VERSION = "1.0.3"
    ICON = "snakefire.png"
    MAC_TRAY_ICON = "snakefire-gray.png"
    COLORS = {
        "normal": None,
        "new": QtGui.QColor(0, 0, 255),
        "alert": QtGui.QColor(255, 0, 0)
    }

    def __init__(self):
        self.DESCRIPTION = self._(self.DESCRIPTION)
        self._pingTimer = None
        self._idleTimer = None
        self._idle = False
        self._lastIdleAnswer = None
        self._worker = None
        self._settings = {}
        self._canConnect = False
        self._cfDisconnected()

        if len(sys.argv) > 1:
            self._qsettings = QtCore.QSettings(
                sys.argv[1],
                QtCore.QSettings.IniFormat if sys.platform.find("win") == 0
                else QtCore.QSettings.NativeFormat)
        else:
            self._qsettings = QtCore.QSettings(self.NAME, self.NAME)

        self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON))
        if platform.system() == "Darwin":
            self._trayIconIcon = QtGui.QIcon(
                ":/icons/{icon}".format(icon=self.MAC_TRAY_ICON))
        else:
            self._trayIconIcon = self._icon
        self.setWindowIcon(self._icon)
        self.setAcceptDrops(True)
        self._setupUI()

        settings = self.getSettings("connection")

        self._canConnect = False
        if settings["subdomain"] and settings["user"] and settings["password"]:
            self._canConnect = True

        self._updateLayout()

        if not self._canConnect:
            self.options()
        elif settings["connect"]:
            self.connectNow()

    def showEvent(self, event):
        if self._trayIcon.isVisible():
            if self._trayIcon.isAlerting():
                self._trayIcon.stopAlert()
            return
        self._trayIcon.show()

    def dragEnterEvent(self, event):
        room = self.getCurrentRoom()
        canUpload = not self._rooms[room.id]["upload"] if room else False
        if canUpload and self._getDropFile(event):
            event.acceptProposedAction()

    def dropEvent(self, event):
        room = self.getCurrentRoom()
        path = self._getDropFile(event)
        if room and path:
            self._upload(room, path)

    def _getDropFile(self, event):
        files = []
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                path = url.path()
                if path and os.path.exists(path) and os.path.isfile(path):
                    try:
                        handle = open(str(path))
                        handle.close()
                        files.append(str(path))
                    except:
                        pass
            if len(files) > 1:
                files = []
        return files[0] if files else None

    def getSetting(self, group, setting):
        settings = self.getSettings(group, asString=False)
        return settings[setting] if setting in settings else None

    def setSetting(self, group, setting, value):
        self._qsettings.beginGroup(group)
        self._qsettings.setValue(setting, value)
        self._qsettings.endGroup()

    def getSettings(self, group, asString=True, reload=False):
        try:
            spell_language = SpellTextEditor.defaultLanguage()
        except enchant.errors.Error:
            spell_language = None
        defaults = {
            "connection": {
                "subdomain": None,
                "user": None,
                "password": None,
                "ssl": False,
                "connect": False,
                "join": False,
                "rooms": []
            },
            "program": {
                "minimize":
                False,
                "spell_language":
                spell_language,
                "away":
                True,
                "away_time":
                10,
                "away_time_between_messages":
                5,
                "away_message":
                self._("I am currently away from {name}").format(
                    name=self.NAME)
            },
            "display": {
                "theme": "default",
                "size": 100,
                "show_join_message": True,
                "show_part_message": True,
                "show_message_timestamps": True
            },
            "alerts": {
                "notify_ping": True,
                "notify_inactive_tab": False,
                "notify_blink": True,
                "notify_notify": True
            },
            "matches": []
        }

        if reload or not group in self._settings:
            settings = defaults[group] if group in defaults else {}

            if group == "matches":
                settings = []
                size = self._qsettings.beginReadArray("matches")
                for i in range(size):
                    self._qsettings.setArrayIndex(i)
                    isRegex = False
                    try:
                        isRegex = True if ["true", "1"].index(
                            str(self._qsettings.value(
                                "regex").toPyObject()).lower()) >= 0 else False
                    except:
                        pass

                    settings.append({
                        'regex':
                        isRegex,
                        'match':
                        self._qsettings.value("match").toPyObject()
                    })
                self._qsettings.endArray()
            else:
                self._qsettings.beginGroup(group)
                for setting in self._qsettings.childKeys():
                    settings[str(setting)] = self._qsettings.value(
                        setting).toPyObject()
                self._qsettings.endGroup()

                boolSettings = []
                if group == "connection":
                    boolSettings += ["ssl", "connect", "join"]
                elif group == "program":
                    boolSettings += ["away", "minimize"]
                elif group == "display":
                    boolSettings += [
                        "show_join_message", "show_part_message",
                        "show_message_timestamps"
                    ]
                elif group == "alerts":
                    boolSettings += [
                        "notify_ping", "notify_inactive_tab", "notify_blink",
                        "notify_notify"
                    ]

                for boolSetting in boolSettings:
                    try:
                        settings[boolSetting] = True if ["true", "1"].index(
                            str(settings[boolSetting]).lower()) >= 0 else False
                    except:
                        settings[boolSetting] = False

                if group == "connection" and settings[
                        "subdomain"] and settings["user"]:
                    settings["password"] = keyring.get_password(
                        self.NAME,
                        str(settings["subdomain"]) + "_" +
                        str(settings["user"]))

            self._settings[group] = settings

        settings = self._settings[group]
        if asString:
            if isinstance(settings, list):
                for i, row in enumerate(settings):
                    for setting in row:
                        if not isinstance(row[setting], bool):
                            settings[i][setting] = str(
                                row[setting]) if row[setting] else ""
            else:
                for setting in settings:
                    if not isinstance(settings[setting], bool):
                        settings[setting] = str(
                            settings[setting]) if settings[setting] else ""

        return settings

    def setSettings(self, group, settings):
        self._settings[group] = settings

        if group == "matches":
            self._qsettings.beginWriteArray("matches")
            for i, setting in enumerate(settings):
                self._qsettings.setArrayIndex(i)
                self._qsettings.setValue("regex", setting["regex"])
                self._qsettings.setValue("match", setting["match"])
            self._qsettings.endArray()
        else:
            self._qsettings.beginGroup(group)
            for setting in self._settings[group]:
                if group != "connection" or setting != "password":
                    self._qsettings.setValue(setting, settings[setting])
                elif settings["subdomain"] and settings["user"]:
                    keyring.set_password(
                        self.NAME,
                        settings["subdomain"] + "_" + settings["user"],
                        settings[setting])
            self._qsettings.endGroup()

            if group == "connection":
                self._canConnect = False
                if settings["subdomain"] and settings["user"] and settings[
                        "password"]:
                    self._canConnect = True
                self._updateLayout()
            elif group == "program":
                if settings["away"] and self._connected:
                    self._setUpIdleTracker()
                else:
                    self._setUpIdleTracker(False)
                if self._editor:
                    if settings["spell_language"]:
                        self._editor.enableSpell(settings["spell_language"])
                    else:
                        self._editor.disableSpell()
            elif group == "display":
                for roomId in self._rooms.keys():
                    if roomId in self._rooms and self._rooms[roomId]["view"]:
                        self._rooms[roomId]["view"].updateTheme(
                            settings["theme"], settings["size"])

    def exit(self):
        self._forceClose = True
        self.close()

    def changeEvent(self, event):
        if self.getSetting("program", "minimize") and event.type(
        ) == QtCore.QEvent.WindowStateChange and self.isMinimized():
            self.hide()
            event.ignore()
        else:
            event.accept()

    def closeEvent(self, event):
        if (not hasattr(self, "_forceClose")
                or not self._forceClose) and self.getSetting(
                    "program", "minimize"):
            self.hide()
            event.ignore()
        else:
            if self.getSetting("connection", "join"):
                self.setSetting(
                    "connection", "rooms",
                    ",".join([str(roomId) for roomId in self._rooms.keys()]))

            self.disconnectNow()

            if hasattr(self, "_workers") and self._workers:
                for worker in self._workers:
                    worker.terminate()
                    worker.wait()

            if hasattr(self, "_worker") and self._worker:
                self._worker.terminate()
                self._worker.wait()

            self.setSetting("window", "size", self.size())
            self.setSetting("window", "position", self.pos())

            event.accept()

    def alerts(self):
        dialog = AlertsDialog(self)
        dialog.open()

    def options(self):
        dialog = OptionsDialog(self)
        dialog.open()

    def about(self):
        dialog = AboutDialog(self)
        dialog.open()

    def connectNow(self):
        if not self._canConnect:
            return

        self._connecting = True
        self.statusBar().showMessage(self._("Connecting with Campfire..."))
        self._updateLayout()

        settings = self.getSettings("connection")

        self._worker = CampfireWorker(settings["subdomain"], settings["user"],
                                      settings["password"], settings["ssl"],
                                      self)
        self._connectWorkerSignals(self._worker)
        self._worker.connect()

    def disconnectNow(self):
        self.statusBar().showMessage(self._("Disconnecting from Campfire..."))
        if self._worker and hasattr(self, "_rooms"):
            # Using keys() since the dict could be changed (by _cfRoomLeft())
            # while iterating on it
            for roomId in self._rooms.keys():
                if roomId in self._rooms and self._rooms[roomId]["room"]:
                    self._worker.leave(self._rooms[roomId]["room"], False)

        self._cfDisconnected()
        self._updateLayout()

    def joinRoom(self, roomIndex=None):
        room = self._roomInIndex(
            roomIndex if roomIndex else self._toolBar["rooms"].currentIndex())
        if not room:
            return

        self._toolBar["join"].setEnabled(False)
        self.statusBar().showMessage(
            unicode(
                self._("Joining room {room}...").format(room=room["name"])))

        self._rooms[room["id"]] = {
            "room": None,
            "stream": None,
            "upload": None,
            "tab": None,
            "view": None,
            "frame": None,
            "usersList": None,
            "topicLabel": None,
            "filesLabel": None,
            "uploadButton": None,
            "uploadLabel": None,
            "uploadWidget": None,
            "newMessages": 0
        }
        self._getWorker().join(room["id"])

    def ping(self):
        if not self._connected:
            return
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["room"]:
                self.updateRoomUsers(roomId, pinging=True)

    def speak(self):
        message = self._editor.document().toPlainText()
        room = self.getCurrentRoom()
        if not room or message.trimmed().isEmpty():
            return

        self._editor.document().clear()

        if message[0] == '/':
            command = QtCore.QString(message)
            separatorIndex = command.indexOf(QtCore.QRegExp('\\s'))
            handled = self.command(
                command.mid(1, separatorIndex - 1),
                command.mid(separatorIndex +
                            1 if separatorIndex >= 0 else command.length()))
            if handled:
                return

        self.statusBar().showMessage(
            unicode(
                self._("Sending message to {room}...").format(room=room.name)))
        self._getWorker().speak(room, unicode(message))

    def command(self, command, args):
        if command.compare(QtCore.QString("away"),
                           QtCore.Qt.CaseInsensitive) == 0:
            self.toggleAway()
            return True

    def uploadFile(self):
        room = self.getCurrentRoom()
        if not room:
            return

        path = QtGui.QFileDialog.getOpenFileName(
            self, self._("Select file to upload"))
        if path:
            self._upload(room, str(path))

    def uploadCancel(self):
        room = self.getCurrentRoom()
        if not room:
            return

        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()
            self._rooms[room.id]["upload"] = None

        self._rooms[room.id]["uploadWidget"].hide()

    def leaveRoom(self, roomId):
        if roomId in self._rooms:
            self.statusBar().showMessage(
                unicode(
                    self._("Leaving room {room}...").format(
                        room=self._rooms[roomId]["room"].name)))
            self._getWorker().leave(self._rooms[roomId]["room"])

    def changeTopic(self):
        room = self.getCurrentRoom()
        if not room:
            return
        topic, ok = QtGui.QInputDialog.getText(
            self, self._("Change topic"),
            unicode(
                self._("Enter new topic for room {room}").format(
                    room=room.name)), QtGui.QLineEdit.Normal, room.topic)
        if ok:
            self.statusBar().showMessage(
                unicode(
                    self._("Changing topic for room {room}...").format(
                        room=room.name)))
            self._getWorker().changeTopic(room, topic)

    def updateRoomUsers(self, roomId=None, pinging=False):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            if not pinging:
                self.statusBar().showMessage(
                    unicode(
                        self._("Getting users in {room}...").format(
                            room=self._rooms[roomId]["room"].name)))
            self._getWorker().users(self._rooms[roomId]["room"], pinging)

    def updateRoomUploads(self, roomId=None):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            self.statusBar().showMessage(
                unicode(
                    self._("Getting uploads from {room}...").format(
                        room=self._rooms[roomId]["room"].name)))
            self._getWorker().uploads(self._rooms[roomId]["room"])

    def getCurrentRoom(self):
        index = self._tabs.currentIndex()
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["tab"] == index:
                return self._rooms[roomId]["room"]

    def toggleAway(self):
        self.setAway(False if self._idle else True)

    def setAway(self, away=True):
        self._idle = away
        self.statusBar().showMessage(
            self._("You are now away")
            if self._idle else self._('You are now active'), 5000)

    def onIdle(self):
        self.setAway(True)

    def onActive(self):
        self.setAway(False)

    def _setUpIdleTracker(self, enable=True):
        if self._idleTimer:
            self._idleTimer.stop()
            self._idleTimer = None

        if enable and IdleTimer.supported():
            self._idleTimer = IdleTimer(
                self,
                int(self.getSetting("program", "away_time")) * 60)
            self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self.onIdle)
            self.connect(self._idleTimer, QtCore.SIGNAL("active()"),
                         self.onActive)
            self._idleTimer.start()

    def _cfStreamMessage(self, room, message, live=True, updateRoom=True):
        if (not message.user or
            (live and message.is_text() and message.is_by_current_user())
                or not room.id in self._rooms):
            return

        view = self._rooms[room.id]["view"]
        if not view:
            return

        alert = False
        alertIsDirectPing = False

        if message.is_text() and not message.is_by_current_user():
            alertIsDirectPing = (QtCore.QString(message.body).indexOf(
                QtCore.QRegExp(
                    "\\s*\\b{name}\\b".format(name=QtCore.QRegExp.escape(
                        self._worker.getUser().name)),
                    QtCore.Qt.CaseInsensitive)) == 0)
            alert = self.getSetting(
                "alerts",
                "notify_ping") if alertIsDirectPing else self._matchesAlert(
                    message.body)

        maximumImageWidth = int(view.size().width() * 0.4)  # 40% of viewport
        renderer = MessageRenderer(self._worker.getApiToken(),
                                   maximumImageWidth,
                                   room,
                                   message,
                                   live=live,
                                   updateRoom=updateRoom,
                                   showTimestamps=self.getSetting(
                                       "display", "show_message_timestamps"),
                                   alert=alert,
                                   alertIsDirectPing=alertIsDirectPing,
                                   parent=self)

        if renderer.needsThread():
            self.connect(
                renderer,
                QtCore.SIGNAL(
                    "render(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"
                ), self._renderMessage)
            renderer.start()
        else:
            self._renderMessage(renderer.render(),
                                room,
                                message,
                                live=live,
                                updateRoom=updateRoom,
                                alert=alert,
                                alertIsDirectPing=alertIsDirectPing)

    def _renderMessage(self,
                       html,
                       room,
                       message,
                       live=True,
                       updateRoom=True,
                       alert=False,
                       alertIsDirectPing=False):
        if (not room.id in self._rooms):
            return

        frame = self._rooms[room.id]["frame"]
        view = self._rooms[room.id]["view"]
        if not frame or not view:
            return

        if not self.getSetting("display", "show_join_message") and (
                message.is_joining() or message.is_leaving()
                or message.is_kick()):
            return

        if html:
            currentScrollbarValue = frame.scrollPosition()
            autoScroll = (currentScrollbarValue == frame.scrollBarMaximum(
                QtCore.Qt.Vertical))
            frame.setHtml(frame.toHtml() + html)
            view.show()
            if autoScroll:
                frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical))
            else:
                frame.scroll(currentScrollbarValue.x(),
                             currentScrollbarValue.y())

            tabIndex = self._rooms[room.id]["tab"]
            tabBar = self._tabs.tabBar()
            isActiveTab = (self.isActiveWindow()
                           and tabIndex == self._tabs.currentIndex())

            if message.is_text() and not isActiveTab:
                self._rooms[room.id]["newMessages"] += 1

            if self._rooms[room.id]["newMessages"] > 0:
                tabBar.setTabText(
                    tabIndex,
                    unicode("{room} ({count})".format(
                        room=room.name,
                        count=self._rooms[room.id]["newMessages"])))

            if not isActiveTab and (
                    alert or self._rooms[room.id]["newMessages"] > 0
            ) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]:
                tabBar.setTabTextColor(
                    tabIndex, self.COLORS["alert" if alert else "new"])

            notifyInactiveTab = self.getSetting("alerts",
                                                "notify_inactive_tab")

            if (not isActiveTab and
                (alert or notifyInactiveTab)) and self.getSetting(
                    "alerts", "notify_blink"):
                self._trayIcon.alert()

            if live and (
                (alert or
                 (not isActiveTab and notifyInactiveTab and message.is_text()))
                    and self.getSetting("alerts", "notify_notify")):
                self._notify(
                    room,
                    unicode("{} says: {}".format(message.user.name,
                                                 message.body)), message.user)

        if updateRoom:
            if (message.is_joining() or message.is_leaving()):
                self.updateRoomUsers(room.id)
            elif message.is_upload():
                self.updateRoomUploads(room.id)
            elif message.is_topic_change(
            ) and not message.is_by_current_user():
                self._cfTopicChanged(room, message.body)

        # Respond to direct pings while being away, but only send an auto-response if last one was sent more than 2 minutes ago
        if live and alertIsDirectPing and self.getSetting(
                "program", "away") and self._idle:
            if self._lastIdleAnswer is None or time.time(
            ) - self._lastIdleAnswer >= (int(
                    self.getSetting("program", "away_time_between_messages")) *
                                         60):
                self._lastIdleAnswer = time.time()
                self._getWorker().speak(
                    room,
                    unicode("{user}: {message}".format(user=message.user.name,
                                                       message=self.getSetting(
                                                           "program",
                                                           "away_message"))))

    def _matchesAlert(self, message):
        matches = False
        searchMatches = self.getSettings("matches")
        for match in searchMatches:
            regex = "\\b{word}\\b".format(word=QtCore.QRegExp.escape(
                match['match'])) if not match['regex'] else match['match']
            if QtCore.QString(message).contains(
                    QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)):
                matches = True
                break
        return matches

    def _cfConnected(self, user, rooms):
        self._connecting = False
        self._connected = True
        self._rooms = {}

        self._toolBar["rooms"].clear()
        for room in rooms:
            self._toolBar["rooms"].addItem(room["name"], room)

        self.statusBar().showMessage(
            unicode(
                self._("{user} connected to Campfire").format(user=user.name)),
            5000)
        self._updateLayout()

        if not self._pingTimer:
            self._pingTimer = QtCore.QTimer(self)
            self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"),
                         self.ping)
        self._pingTimer.start(60000)  # Ping every minute

        if self.getSetting("program", "away"):
            self._setUpIdleTracker()

        if self.getSetting("connection", "join"):
            rooms = self.getSetting("connection", "rooms")
            if rooms:
                for roomId in rooms.split(","):
                    count = self._toolBar["rooms"].count()
                    if count:
                        roomIndex = None
                        for i in range(count):
                            data = self._toolBar["rooms"].itemData(i)
                            if not data.isNull():
                                data = data.toMap()
                                for key in data:
                                    if str(key) == "id" and str(
                                            data[key].toString()) == roomId:
                                        roomIndex = i
                                        break
                                if roomIndex is not None:
                                    break
                        if roomIndex is not None:
                            self.joinRoom(roomIndex)

    def _cfDisconnected(self):
        if self._pingTimer:
            self._pingTimer.stop()
            self._pingTimer = None

        if self._idleTimer:
            self._setUpIdleTracker(False)

        self._connecting = False
        self._connected = False
        self._rooms = {}
        self._worker = None
        self.statusBar().clearMessage()

    def _cfRoomJoined(self, room, messages=[], rejoined=False):
        if room.id not in self._rooms:
            return

        if not rejoined:
            self._rooms[room.id].update(self._setupRoomUI(room))
            self._rooms[room.id]["room"] = room
        self._rooms[room.id]["stream"] = self._worker.getStream(room)
        self.updateRoomUsers(room.id)
        self.updateRoomUploads(room.id)
        if not rejoined:
            self.statusBar().showMessage(
                unicode(self._("Joined room {room}").format(room=room.name)),
                5000)
        self._updatedRoomsList()
        if not rejoined and messages:
            for message in messages:
                self._cfStreamMessage(room,
                                      message,
                                      live=False,
                                      updateRoom=False)

    def _cfSpoke(self, room, message):
        self._cfStreamMessage(room, message, live=False)
        self.statusBar().clearMessage()

    def _cfRoomLeft(self, room):
        if self._rooms[room.id]["stream"]:
            self._rooms[room.id]["stream"].stop().join()
        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()

        self._tabs.removeTab(self._rooms[room.id]["tab"])
        del self._rooms[room.id]
        self.statusBar().showMessage(
            unicode(self._("Left room {room}").format(room=room.name)), 5000)
        self._updatedRoomsList()

    def _cfRoomUsers(self, room, users, pinging=False):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        if not pinging:
            self.statusBar().clearMessage()
        self._rooms[room.id]["usersList"].clear()
        for user in users:
            item = QtGui.QListWidgetItem(user["name"])
            item.setData(QtCore.Qt.UserRole, user)
            self._rooms[room.id]["usersList"].addItem(item)

    def _cfRoomUploads(self, room, uploads):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        self.statusBar().clearMessage()
        label = self._rooms[room.id]["filesLabel"]
        if uploads:
            html = ""
            for upload in uploads:
                html += "{br}&bull; <a href=\"{url}\">{name}</a>".format(
                    br="<br />" if html else "",
                    url=upload["full_url"],
                    name=upload["name"])
            html = unicode("{text}<br />{html}".format(
                text=self._("Latest uploads:"), html=html))

            label.setText(html)
            if not label.isVisible():
                label.show()
        elif label.isVisible():
            label.setText("")
            label.hide()

    def _cfUploadProgress(self, room, current, total):
        if not room.id in self._rooms:
            return

        progressBar = self._rooms[room.id]["uploadProgressBar"]
        if not self._rooms[room.id]["uploadWidget"].isVisible():
            self._rooms[room.id]["uploadWidget"].show()
            progressBar.setMaximum(total)

        progressBar.setValue(current)

    def _cfUploadFinished(self, room):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["upload"].join()
        self._rooms[room.id]["upload"] = None
        self._rooms[room.id]["uploadWidget"].hide()

    def _cfTopicChanged(self, room, topic):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["topicLabel"].setText(topic)
        self.statusBar().clearMessage()

    def _cfConnectError(self, error):
        self._cfDisconnected()
        self._updateLayout()
        self._cfError(error)

    def _cfError(self, error):
        self.statusBar().clearMessage()
        if not self._connected:
            QtGui.QMessageBox.critical(
                self, "Error",
                self._("Error while connecting: {error}".format(
                    error=str(error))))
        else:
            QtGui.QMessageBox.critical(self, "Error", str(error))

    def _cfRoomError(self, error, room):
        self.statusBar().clearMessage()
        if isinstance(error, RuntimeError):
            (code, message) = error
            if code == 401:
                self.statusBar().showMessage(
                    unicode(
                        self._(
                            "Disconnected from room. Rejoining room {room}..."
                        ).format(room=room.name)), 5000)
                self._rooms[room.id]["stream"].stop().join()
                self._getWorker().join(room.id, True)
                return
        QtGui.QMessageBox.critical(self, "Error", str(error))

    def _roomSelected(self, index):
        self._updatedRoomsList(index)

    def _upload(self, room, path):
        self._rooms[room.id]["upload"] = self._worker.upload(room, path)
        self._updateRoomLayout()

    def _roomTabClose(self, tabIndex):
        for roomId in self._rooms:
            if self._rooms[roomId]["tab"] == tabIndex:
                self.leaveRoom(roomId)
                break

    def _roomTabFocused(self):
        tabIndex = self._tabs.currentIndex()
        if tabIndex < 0 or not self.isActiveWindow():
            return

        room = self._roomInTabIndex(tabIndex)
        if not room:
            return

        tabBar = self._tabs.tabBar()

        if self._rooms[room.id]["newMessages"] > 0:
            self._rooms[room.id]["newMessages"] = 0
            tabBar.setTabText(tabIndex, room.name)

        if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]:
            tabBar.setTabTextColor(tabIndex, self.COLORS["normal"])

        self._updateRoomLayout()

    def _roomInTabIndex(self, index):
        room = None
        for key in self._rooms:
            if self._rooms[key]["tab"] == index:
                room = self._rooms[key]["room"]
                break
        return room

    def _roomInIndex(self, index):
        room = {}
        data = self._toolBar["rooms"].itemData(index)
        if not data.isNull():
            data = data.toMap()
            for key in data:
                room[str(key)] = unicode(data[key].toString())
        return room

    def _connectWorkerSignals(self, worker):
        self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"),
                     self._cfError)
        self.connect(worker,
                     QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfConnected)
        self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"),
                     self._cfConnectError)
        self.connect(
            worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"),
            self._cfRoomError)
        self.connect(
            worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"),
            self._cfRoomError)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfRoomJoined)
        self.connect(worker,
                     QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfSpoke)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfStreamMessage)
        self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"),
                     self._cfRoomLeft)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfRoomUsers)
        self.connect(worker,
                     QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"),
                     self._cfRoomUploads)
        self.connect(
            worker,
            QtCore.SIGNAL(
                "uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"),
            self._cfUploadProgress)
        self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"),
                     self._cfUploadFinished)
        self.connect(
            worker,
            QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"),
            self._cfTopicChanged)

    def _getWorker(self):
        if not hasattr(self, "_workers"):
            self._workers = []

        if self._workers:
            for worker in self._workers:
                if worker.isFinished():
                    return worker

        worker = copy.copy(self._worker)
        self._connectWorkerSignals(worker)
        self._workers.append(worker)
        return worker

    def _updatedRoomsList(self, index=None):
        if not index:
            index = self._toolBar["rooms"].currentIndex()

        room = self._roomInIndex(index)

        self._toolBar["join"].setEnabled(False)
        if not room or room["id"] not in self._rooms:
            self._toolBar["join"].setEnabled(True)

        centralWidget = self.centralWidget()
        if not self._tabs.count():
            centralWidget.hide()
        else:
            centralWidget.show()

    def _notify(self, room, message, user):
        raise NotImplementedError("_notify() must be implemented")

    def _updateRoomLayout(self):
        room = self.getCurrentRoom()
        if room:
            canUpload = not self._rooms[room.id]["upload"]
            uploadButton = self._rooms[room.id]["uploadButton"]
            if ((canUpload and not uploadButton.isEnabled())
                    or (not canUpload and uploadButton.isEnabled())):
                uploadButton.setEnabled(canUpload)

    def _updateLayout(self):
        self._menus["file"]["connect"].setEnabled(not self._connected
                                                  and self._canConnect
                                                  and not self._connecting)
        self._menus["file"]["disconnect"].setEnabled(self._connected)

        roomsEmpty = self._toolBar["rooms"].count(
        ) == 1 and self._toolBar["rooms"].itemData(0).isNull()
        if not roomsEmpty and (not self._connected
                               or not self._toolBar["rooms"].count()):
            self._toolBar["rooms"].clear()
            self._toolBar["rooms"].addItem(self._("No rooms available"))
            self._toolBar["rooms"].setEnabled(False)
        elif not roomsEmpty:
            self._toolBar["rooms"].setEnabled(True)

        self._toolBar["roomsLabel"].setEnabled(
            self._toolBar["rooms"].isEnabled())
        self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled())

    def _setupRoomUI(self, room):
        topic = room.topic if room.topic else ""
        topicLabel = ClickableQLabel(topic)
        topicLabel.setToolTip(self._("Click here to change room's topic"))
        topicLabel.setWordWrap(True)
        self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic)

        view = SnakeFireWebView(self)
        view.setSizePolicy(QtGui.QSizePolicy.Expanding,
                           QtGui.QSizePolicy.Expanding)
        frame = view.page().mainFrame()

        #Send all link clicks to systems web browser
        view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)

        def linkClicked(url):
            QtGui.QDesktopServices.openUrl(url)

        view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"),
                     linkClicked)

        # Support auto scroll when needed
        def autoScroll(size):
            frame.scroll(0, size.height())

        frame.connect(frame,
                      QtCore.SIGNAL("contentsSizeChanged (const QSize&)"),
                      autoScroll)

        usersList = QtGui.QListWidget()

        filesLabel = QtGui.QLabel("")
        filesLabel.setOpenExternalLinks(True)
        filesLabel.setWordWrap(True)
        filesLabel.hide()

        uploadButton = QtGui.QPushButton(self._("&Upload new file"))
        self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile)

        uploadProgressBar = QtGui.QProgressBar()
        uploadProgressLabel = QtGui.QLabel(self._("Uploading:"))

        uploadCancelButton = QtGui.QPushButton(self._("Cancel"))
        self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"),
                     self.uploadCancel)

        uploadLayout = QtGui.QHBoxLayout()
        uploadLayout.addWidget(uploadProgressLabel)
        uploadLayout.addWidget(uploadProgressBar)
        uploadLayout.addWidget(uploadCancelButton)

        uploadWidget = QtGui.QWidget()
        uploadWidget.setLayout(uploadLayout)
        uploadWidget.hide()

        leftFrameLayout = QtGui.QVBoxLayout()
        leftFrameLayout.addWidget(topicLabel)
        leftFrameLayout.addWidget(view)
        leftFrameLayout.addWidget(uploadWidget)

        rightFrameLayout = QtGui.QVBoxLayout()
        rightFrameLayout.addWidget(usersList)
        rightFrameLayout.addWidget(filesLabel)
        rightFrameLayout.addWidget(uploadButton)
        rightFrameLayout.addStretch(1)

        leftFrame = QtGui.QWidget()
        leftFrame.setLayout(leftFrameLayout)

        rightFrame = QtGui.QWidget()
        rightFrame.setLayout(rightFrameLayout)

        splitter = QtGui.QSplitter()
        splitter.addWidget(leftFrame)
        splitter.addWidget(rightFrame)
        splitter.setSizes(
            [splitter.size().width() * 0.75,
             splitter.size().width() * 0.25])

        index = self._tabs.addTab(splitter, room.name)
        self._tabs.setCurrentIndex(index)

        if not self.COLORS["normal"]:
            self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index)
        else:
            self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"])

        return {
            "tab": index,
            "view": view,
            "frame": frame,
            "usersList": usersList,
            "topicLabel": topicLabel,
            "filesLabel": filesLabel,
            "uploadButton": uploadButton,
            "uploadWidget": uploadWidget,
            "uploadProgressBar": uploadProgressBar,
            "uploadProgressLabel": uploadProgressLabel
        }

    def _setupUI(self):
        self.setWindowTitle(self.NAME)

        self._addMenu()
        self._addToolbar()

        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"),
                     self._roomTabFocused)
        self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"),
                     self._roomTabClose)

        self._editor = SpellTextEditor(lang=self.getSetting(
            "program", "spell_language"),
                                       mainFrame=self)

        speakButton = QtGui.QPushButton(self._("&Send"))
        self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

        grid = QtGui.QGridLayout()
        grid.setRowStretch(0, 1)
        grid.addWidget(self._tabs, 0, 0, 1, -1)
        grid.addWidget(self._editor, 2, 0)
        grid.addWidget(speakButton, 2, 1)

        widget = QtGui.QWidget()
        widget.setLayout(grid)
        self.setCentralWidget(widget)

        tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
        self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"),
                     self._roomTabFocused)
        widget.installEventFilter(tabWidgetFocusEventFilter)

        self.centralWidget().hide()

        size = self.getSetting("window", "size")

        if not size:
            size = QtCore.QSize(640, 480)

        self.resize(size)

        position = self.getSetting("window", "position")
        if not position:
            screen = QtGui.QDesktopWidget().screenGeometry()
            position = QtCore.QPoint((screen.width() - size.width()) / 2,
                                     (screen.height() - size.height()) / 2)

        self.move(position)

        self._updateLayout()

        menu = QtGui.QMenu(self)
        menu.addAction(self._menus["file"]["connect"])
        menu.addAction(self._menus["file"]["disconnect"])
        menu.addSeparator()
        menu.addAction(self._menus["file"]["exit"])

        self._trayIcon = Systray(self._trayIconIcon, self)
        self._trayIcon.setContextMenu(menu)
        self._trayIcon.setToolTip(self.DESCRIPTION)

    def _addMenu(self):
        self._menus = {
            "file": {
                "connect":
                self._createAction(self._("&Connect"),
                                   self.connectNow,
                                   icon="connect.png"),
                "disconnect":
                self._createAction(self._("&Disconnect"),
                                   self.disconnectNow,
                                   icon="disconnect.png"),
                "exit":
                self._createAction(self._("E&xit"), self.exit)
            },
            "settings": {
                "alerts":
                self._createAction(self._("&Alerts..."),
                                   self.alerts,
                                   icon="alerts.png"),
                "options":
                self._createAction(self._("&Options..."), self.options)
            },
            "help": {
                "about": self._createAction(self._("A&bout"), self.about)
            }
        }

        menu = self.menuBar()

        file_menu = menu.addMenu(self._("&File"))
        file_menu.addAction(self._menus["file"]["connect"])
        file_menu.addAction(self._menus["file"]["disconnect"])
        file_menu.addSeparator()
        file_menu.addAction(self._menus["file"]["exit"])

        settings_menu = menu.addMenu(self._("S&ettings"))
        settings_menu.addAction(self._menus["settings"]["alerts"])
        settings_menu.addSeparator()
        settings_menu.addAction(self._menus["settings"]["options"])

        help_menu = menu.addMenu(self._("&Help"))
        help_menu.addAction(self._menus["help"]["about"])

    def _addToolbar(self):
        self._toolBar = {
            "connect":
            self._menus["file"]["connect"],
            "disconnect":
            self._menus["file"]["disconnect"],
            "roomsLabel":
            QtGui.QLabel(self._("Rooms:")),
            "rooms":
            QtGui.QComboBox(),
            "join":
            self._createAction(self._("Join room"),
                               self.joinRoom,
                               icon="join.png"),
            "alerts":
            self._menus["settings"]["alerts"]
        }

        self.connect(self._toolBar["rooms"],
                     QtCore.SIGNAL("currentIndexChanged(int)"),
                     self._roomSelected)

        toolBar = self.toolBar()
        toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        toolBar.addAction(self._toolBar["connect"])
        toolBar.addAction(self._toolBar["disconnect"])
        toolBar.addSeparator()
        toolBar.addWidget(self._toolBar["roomsLabel"])
        toolBar.addWidget(self._toolBar["rooms"])
        toolBar.addAction(self._toolBar["join"])
        toolBar.addSeparator()
        toolBar.addAction(self._toolBar["alerts"])

    def _createAction(self,
                      text,
                      slot=None,
                      shortcut=None,
                      icon=None,
                      tip=None,
                      checkable=False,
                      signal="triggered()"):
        """ Create an action """
        action = QtGui.QAction(text, self)
        if icon is not None:
            if not isinstance(icon, QtGui.QIcon):
                action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon)))
            else:
                action.setIcon(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, QtCore.SIGNAL(signal), slot)
        if checkable:
            action.setCheckable(True)
        return action

    def _(self, string, module=None):
        return str(
            QtCore.QCoreApplication.translate(module or Snakefire.NAME,
                                              string))
Example #18
0
    def _setupUI(self):
        self.setWindowTitle(self.NAME)

        self._addMenu()
        self._addToolbar()

        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"),
                     self._roomTabFocused)
        self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"),
                     self._roomTabClose)

        self._editor = SpellTextEditor(lang=self.getSetting(
            "program", "spell_language"),
                                       mainFrame=self)

        speakButton = QtGui.QPushButton(self._("&Send"))
        self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

        grid = QtGui.QGridLayout()
        grid.setRowStretch(0, 1)
        grid.addWidget(self._tabs, 0, 0, 1, -1)
        grid.addWidget(self._editor, 2, 0)
        grid.addWidget(speakButton, 2, 1)

        widget = QtGui.QWidget()
        widget.setLayout(grid)
        self.setCentralWidget(widget)

        tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
        self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"),
                     self._roomTabFocused)
        widget.installEventFilter(tabWidgetFocusEventFilter)

        self.centralWidget().hide()

        size = self.getSetting("window", "size")

        if not size:
            size = QtCore.QSize(640, 480)

        self.resize(size)

        position = self.getSetting("window", "position")
        if not position:
            screen = QtGui.QDesktopWidget().screenGeometry()
            position = QtCore.QPoint((screen.width() - size.width()) / 2,
                                     (screen.height() - size.height()) / 2)

        self.move(position)

        self._updateLayout()

        menu = QtGui.QMenu(self)
        menu.addAction(self._menus["file"]["connect"])
        menu.addAction(self._menus["file"]["disconnect"])
        menu.addSeparator()
        menu.addAction(self._menus["file"]["exit"])

        self._trayIcon = Systray(self._trayIconIcon, self)
        self._trayIcon.setContextMenu(menu)
        self._trayIcon.setToolTip(self.DESCRIPTION)
Example #19
0
class zcswebapp(QtGui.QMainWindow):

    plugins = True
    debug = False
    forceClose = False
    messages = 0

    def __init__(self, parent = None, settings_path = ""):
        super(zcswebapp, self).__init__(parent)
        self.setWindowTitle('zcswebapp')
        self.settings_path = settings_path
        self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('zcswebapp.png'))
        self.settings = QSettings(self.settings_path + '/zcswebapp.cfg', QSettings.IniFormat)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id("zcswebapp.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.leftPane = LeftPane(self)
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        self.stackedWidget = QtGui.QStackedWidget()
        self.stackedWidget.addWidget(webView)
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(zcswebapp.minimized)
        self.installEventFilter(self)
        if self.identifier is None:
            webView.load(QtCore.QUrl(Resources.SIGNIN_URL))
        else:
            webView.load(QtCore.QUrl(self.domain()))
        webView.show()

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # Required by Youtube videos (HTML5 video support only on Qt5)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins)
        # We don't want Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False)
        # We don't need History
        QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, True)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.showMaximized()

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addMenu(self):
        self.menus = {
            "file": {
                "preferences": self.createAction("Preferences", self.current().preferences),
                "systray":     self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":     self.createAction("Sign in to Another Team", self.current().addTeam),
                "signout":     self.createAction("Signout", self.current().logout),
                "close":       self.createAction("Close", self.close, QKeySequence.Close),
                "exit":        self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo":        self.current().pageAction(QtWebKit.QWebPage.Undo),
                "redo":        self.current().pageAction(QtWebKit.QWebPage.Redo),
                "cut":         self.current().pageAction(QtWebKit.QWebPage.Cut),
                "copy":        self.current().pageAction(QtWebKit.QWebPage.Copy),
                "paste":       self.current().pageAction(QtWebKit.QWebPage.Paste),
                "back":        self.current().pageAction(QtWebKit.QWebPage.Back),
                "forward":     self.current().pageAction(QtWebKit.QWebPage.Forward),
                "reload":      self.current().pageAction(QtWebKit.QWebPage.Reload)
            },
            "view": {
                "zoomin":      self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":     self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut),
                "reset":       self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen":  self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11)        
            },
            "help": {
                "help":       self.createAction("Help and Feedback", self.current().help, QKeySequence.HelpContents),
                "center":     self.createAction("Slack Help Center", self.current().helpCenter),
                "about":      self.createAction("About", self.current().about)
             }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["back"])
        editMenu.addAction(self.menus["edit"]["forward"])
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        if shortcut is not None:
            action.setShortcut(shortcut)
        action.triggered.connect(slot)
        if checkable:
            action.setCheckable(True)
        return action

    def domain(self):
        if self.identifier.endswith(".slack.com"):
            return self.identifier
        else:
            return "https://"+self.identifier+".slack.com"

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if teams is not None and len(teams) > 1:
            self.leftPane.show()
            for t in teams:
                try:
                    self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_88'], t == teams[0])
                except:
                    self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], '', t == teams[0])

    def switchTo(self, url):
        qUrl = QtCore.QUrl(url)
        index = -1
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                index = i
                break
        if index != -1:
            self.stackedWidget.setCurrentIndex(index)
        else:
            webView = Wrapper(self)
            webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
            webView.load(qUrl)
            webView.show()
            self.stackedWidget.addWidget(webView)
            self.stackedWidget.setCurrentWidget(webView)
        self.quicklist(self.current().listChannels())
        self.enableMenus(self.current().isConnected())
        # Save the last used team as default
        self.settings.setValue("Domain", 'https://'+qUrl.host())

    def eventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1:   self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8)
            # Ctrl + Shift + <key>
            if (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier) and (QtGui.QApplication.keyboardModifiers() & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V: self.current().createSnippet()
        return QtGui.QMainWindow.eventFilter(self, obj, event);

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.forceClose = True
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            ql = Dbusmenu.Menuitem.new()
            self.launcher.set_property("quicklist", ql)
            if channels is not None:
                for c in channels:
                    if c['is_member']:
                        item = Dbusmenu.Menuitem.new ()
                        item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name'])
                        item.property_set ("id", c['name'])
                        item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message):
        self.notifier.notify(title, message)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()

    def count(self):
        total = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            if widget.messages == 0:
                self.leftPane.stopAlert(widget.team())
            else:
                self.leftPane.alert(widget.team())
            total+=widget.messages
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
        self.messages = total
Example #20
0
class Snakefire(object):
    DOMAIN = "snakefire.org"
    NAME = "Snakefire"
    DESCRIPTION = "Snakefire: Campfire Linux Client"
    VERSION = "1.0.1"
    ICON = "snakefire.png"
    COLORS = {
        "normal": None,
        "new": QtGui.QColor(0, 0, 255),
        "alert": QtGui.QColor(255, 0, 0)
    }
    MESSAGES = {
        "alert": '<div class="alert"><span class="time">[{time}]</span> <span class="author">{user}</span>: {message}</div>',
        "image": '<span class="upload image"><a href="{url}"><img src="data:image/{type};base64,{data}" title="{name}" {attribs} /></a></span>',
        "join": '<div class="joined">--&gt; {user} joined {room}</div>',
        "leave": '<div class="left">&lt;-- {user} has left {room}</div>',
        "message_self": '<div class="message"><span class="time">[{time}]</span> <span class="author self">{user}</span>: {message}</div>',
        "message": '<div class="message"><span class="time">[{time}]</span> <span class="author">{user}</span>: {message}</div>',
        "paste": '<div class="paste">{message}</div>',
        "upload": '<span class="upload"><a href="{url}">{name}</a></span>',
        "topic": '<div class="topic">{user} changed topic to <span class="new_topic">{topic}</span></div>',
        "tweet": '<div class="tweet"><a href="{url_user}">{user}</a> <a href="{url}">tweeted</a>: {message}</div>'
    }

    def __init__(self):
        self.DESCRIPTION = self._(self.DESCRIPTION)
        self._pingTimer = None
        self._idleTimer = None
        self._idle = False
        self._worker = None
        self._settings = {}
        self._canConnect = False
        self._cfDisconnected()
        self._qsettings = QtCore.QSettings()
        self._icon = QtGui.QIcon(":/icons/{icon}".format(icon=self.ICON))
        self.setWindowIcon(self._icon)
        self.setAcceptDrops(True)
        self._setupUI()

        settings = self.getSettings("connection")

        self._canConnect = False
        if settings["subdomain"] and settings["user"] and settings["password"]:
            self._canConnect = True

        self._updateLayout()

        if settings["connect"]:
            self.connectNow()

    def showEvent(self, event):
        if self._trayIcon.isVisible():
            if self._trayIcon.isAlerting():
                self._trayIcon.stopAlert()
            return
        self._trayIcon.show()

    def dragEnterEvent(self, event):
        room = self.getCurrentRoom()
        canUpload = not self._rooms[room.id]["upload"] if room else False
        if canUpload and self._getDropFile(event):
            event.acceptProposedAction()

    def dropEvent(self, event):
        room = self.getCurrentRoom()
        path = self._getDropFile(event)
        if room and path:
            self._upload(room, path)

    def _getDropFile(self, event):
        files = []
        urls = event.mimeData().urls()
        if urls:
            for url in urls:
                path = url.path()
                if path and os.path.exists(path) and os.path.isfile(path):
                    try:
                        handle = open(str(path))
                        handle.close()
                        files.append(str(path))
                    except Exception as e:
                        pass
            if len(files) > 1:
               files = []
        return files[0] if files else None

    def getSetting(self, group, setting):
        settings = self.getSettings(group, asString=False);
        return settings[setting] if setting in settings else None

    def setSetting(self, group, setting, value):
        self._qsettings.beginGroup(group);
        self._qsettings.setValue(setting, value)
        self._qsettings.endGroup();

    def getSettings(self, group, asString=True, reload=False):
        defaults = {
            "connection": {
                "subdomain": None,
                "user": None,
                "password": None,
                "ssl": False,
                "connect": False,
                "join": False,
                "rooms": []
            },
            "program": {
                "minimize": False,
                "away": True,
                "away_time": 10,
                "away_message": self._("I am currently away from {name}").format(name=self.NAME)
            },
            "display": {
                "theme": "default",
                "size": 100,
                "show_join_message": True,
                "show_part_message": True
            },
            "alerts": {
                "notify_inactive_tab": False,
                "matches": "Snakefire;python"
            }
        }

        if reload or not group in self._settings:
            settings = defaults[group] if group in defaults else {}

            self._qsettings.beginGroup(group);
            for setting in self._qsettings.childKeys():
                settings[str(setting)] = self._qsettings.value(setting).toPyObject()
            self._qsettings.endGroup();

            boolSettings = []
            if group == "connection":
                boolSettings += ["ssl", "connect", "join"]
            elif group == "program":
                boolSettings += ["away", "minimize"]
            elif group == "display":
                boolSettings += ["show_join_message", "show_part_message"]
            elif group == "alerts":
                boolSettings += ["notify_inactive_tab"]

            for boolSetting in boolSettings:
                try:
                    settings[boolSetting] = True if ["true", "1"].index(str(settings[boolSetting]).lower()) >= 0 else False
                except:
                    settings[boolSetting] = False

            if group == "connection" and settings["subdomain"] and settings["user"]:
                settings["password"] = keyring.get_password(self.NAME, str(settings["subdomain"])+"_"+str(settings["user"])) 

            self._settings[group] = settings

        settings = self._settings[group]
        if asString:
            for setting in settings:
                if not isinstance(settings[setting], bool):
                    settings[setting] = str(settings[setting]) if settings[setting] else ""
        return settings

    def setSettings(self, group, settings):
        self._settings[group] = settings;

        self._qsettings.beginGroup(group);
        for setting in self._settings[group]:
            if group != "connection" or setting != "password":
                self._qsettings.setValue(setting, settings[setting])
            elif settings["subdomain"] and settings["user"]:
                keyring.set_password(self.NAME, settings["subdomain"]+"_"+settings["user"], settings[setting]) 
        self._qsettings.endGroup();

        if group == "connection":
            self._canConnect = False
            if settings["subdomain"] and settings["user"] and settings["password"]:
                self._canConnect = True
            self._updateLayout()
        elif group == "program":
            if settings["away"] and self._connected:
                self._setUpIdleTracker()
            else:
                self._setUpIdleTracker(False)
        elif group == "display":
            for roomId in self._rooms.keys():
                if roomId in self._rooms and self._rooms[roomId]["view"]:
                    self._rooms[roomId]["view"].updateTheme(settings["theme"], settings["size"])

    def exit(self):
        self._forceClose = True
        self.close()

    def changeEvent(self, event):
        if self.getSetting("program", "minimize") and event.type() == QtCore.QEvent.WindowStateChange and self.isMinimized():
            self.hide()
            event.ignore()
        else:
            event.accept()

    def closeEvent(self, event):
        if (not hasattr(self, "_forceClose") or not self._forceClose) and self.getSetting("program", "minimize"):
            self.hide()
            event.ignore()
        else:
            if self.getSetting("connection", "join"):
                self.setSetting("connection", "rooms", ",".join([str(roomId) for roomId in self._rooms.keys()]))

            self.disconnectNow()

            if hasattr(self, "_workers") and self._workers:
                for worker in self._workers:
                    worker.terminate()
                    worker.wait()

            if hasattr(self, "_worker") and self._worker:
                self._worker.terminate()
                self._worker.wait()

            self.setSetting("window", "size", self.size())
            self.setSetting("window", "position", self.pos())

            event.accept()

    def alerts(self):
        dialog = AlertsDialog(self)
        dialog.open()

    def options(self):
        dialog = OptionsDialog(self)
        dialog.open()

    def connectNow(self):
        if not self._canConnect:
            return

        self._connecting = True
        self.statusBar().showMessage(self._("Connecting with Campfire..."))
        self._updateLayout()

        settings = self.getSettings("connection")

        self._worker = CampfireWorker(settings["subdomain"], settings["user"], settings["password"], settings["ssl"], self)
        self._connectWorkerSignals(self._worker)
        self._worker.connect()

    def disconnectNow(self):
        self.statusBar().showMessage(self._("Disconnecting from Campfire..."))
        if self._worker and hasattr(self, "_rooms"):
            # Using keys() since the dict could be changed (by _cfRoomLeft())
            # while iterating on it
            for roomId in self._rooms.keys():
                if roomId in self._rooms and self._rooms[roomId]["room"]:
                    self._worker.leave(self._rooms[roomId]["room"], False)
         
        self._cfDisconnected()
        self._updateLayout()

    def joinRoom(self, roomIndex=None):
        room = self._roomInIndex(roomIndex if roomIndex else self._toolBar["rooms"].currentIndex())
        if not room:
            return

        self._toolBar["join"].setEnabled(False)
        self.statusBar().showMessage(self._("Joining room {room}...").format(room=room["name"]))

        self._rooms[room["id"]] = {
            "room": None,
            "stream": None,
            "upload": None,
            "tab": None,
            "view": None,
            "frame": None,
            "usersList": None,
            "topicLabel": None,
            "filesLabel": None,
            "uploadButton": None,
            "uploadLabel": None,
            "uploadWidget": None,
            "newMessages": 0
        }
        self._getWorker().join(room["id"])

    def ping(self):
        if not self._connected:
            return
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["room"]:
                self.updateRoomUsers(roomId, pinging=True)

    def speak(self):
        message = self._editor.document().toPlainText()
        room = self.getCurrentRoom()
        if not room or message.trimmed().isEmpty():
            return

        self._editor.document().clear()

        if message[0] == '/':
            command = QtCore.QString(message)
            separatorIndex = command.indexOf(QtCore.QRegExp('\\s'));
            handled = self.command(command.mid(1, separatorIndex-1), command.mid(separatorIndex + 1 if separatorIndex >= 0 else command.length()))
            if handled:
                return

        self.statusBar().showMessage(self._("Sending message to {room}...").format(room=room.name))
        self._getWorker().speak(room, unicode(message))

    def command(self, command, args):
        if command.compare(QtCore.QString("away"), QtCore.Qt.CaseInsensitive) == 0:
            self.toggleAway()
            return True

    def uploadFile(self):
        room = self.getCurrentRoom()
        if not room:
            return

        path = QtGui.QFileDialog.getOpenFileName(self, self._("Select file to upload"))
        if path:
            self._upload(room, str(path))

    def uploadCancel(self):
        room = self.getCurrentRoom()
        if not room:
            return

        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()
            self._rooms[room.id]["upload"] = None

        self._rooms[room.id]["uploadWidget"].hide()

    def leaveRoom(self, roomId):
        if roomId in self._rooms:
            self.statusBar().showMessage(self._("Leaving room {room}...").format(room=self._rooms[roomId]["room"].name))
            self._getWorker().leave(self._rooms[roomId]["room"])

    def changeTopic(self):
        room = self.getCurrentRoom()
        if not room:
            return
        topic, ok = QtGui.QInputDialog.getText(self,
            self._("Change topic"),
            self._("Enter new topic for room {room}").format(room=room.name),
            QtGui.QLineEdit.Normal,
            room.topic
        )
        if ok:
            self.statusBar().showMessage(self._("Changing topic for room {room}...").format(room=room.name))
            self._getWorker().changeTopic(room, topic)

    def updateRoomUsers(self, roomId = None, pinging = False):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            if not pinging:
                self.statusBar().showMessage(self._("Getting users in {room}...").format(room=self._rooms[roomId]["room"].name))
            self._getWorker().users(self._rooms[roomId]["room"], pinging)

    def updateRoomUploads(self, roomId = None):
        if not roomId:
            room = self.getCurrentRoom()
            if room:
                roomId = room.id
        if roomId in self._rooms:
            self.statusBar().showMessage(self._("Getting uploads from {room}...").format(room=self._rooms[roomId]["room"].name))
            self._getWorker().uploads(self._rooms[roomId]["room"])

    def getCurrentRoom(self):
        index = self._tabs.currentIndex()
        for roomId in self._rooms.keys():
            if roomId in self._rooms and self._rooms[roomId]["tab"] == index:
                return self._rooms[roomId]["room"]

    def toggleAway(self):
        self.setAway(False if self._idle else True)

    def setAway(self, away=True):
        self._idle = away
        self.statusBar().showMessage(self._("You are now away") if self._idle else self._('You are now active'), 5000)

    def _idle(self):
        self.setAway(True)

    def _active(self):
        self.setAway(False)

    def _setUpIdleTracker(self, enable=True):
        if self._idleTimer:
            self._idleTimer.stop().wait()
            self._idleTimer = None

        if enable:
            try:
                self._idleTimer = IdleTimer(self, self.getSetting("program", "away_time") * 60)
                self.connect(self._idleTimer, QtCore.SIGNAL("idle()"), self._idle)
                self.connect(self._idleTimer, QtCore.SIGNAL("active()"), self._active)
                self._idleTimer.start()
            except:
                self._idleTimer = None

    def _cfStreamMessage(self, room, message, live=True, updateRoom=True):
        if (
            not message.user or 
            (live and message.is_text() and message.is_by_current_user()) or
            not room.id in self._rooms
        ):
            return

        view = self._rooms[room.id]["view"]
        frame = self._rooms[room.id]["frame"]
        if not view and frame:
            return

        notify = True
        alert = False
        alertIsDirectPing = False

        if message.is_text() and not message.is_by_current_user():
            alertIsDirectPing = QtCore.QString(message.body).contains(QtCore.QRegExp("\\b{name}\\b".format(name=self._worker.getUser().name), QtCore.Qt.CaseInsensitive))
            alert = True if alertIsDirectPing else self._matchesAlert(message.body)

        html = None
        if message.is_joining() and self.getSetting("display", "show_join_message"):
            html = self.MESSAGES["join"].format(user=message.user.name, room=room.name)
        elif message.is_leaving() and self.getSetting("display", "show_join_message"):
            html = self.MESSAGES["leave"].format(user=message.user.name, room=room.name)
        elif message.is_text() or message.is_upload():
            if message.body:
                body = self._plainTextToHTML(message.tweet["tweet"] if message.is_tweet() else message.body)

            if message.is_tweet():
                body = self.MESSAGES["tweet"].format(
                    url_user = "******".format(user=message.tweet["user"]),
                    user = message.tweet["user"], 
                    url = message.tweet["url"],
                    message = body
                )
            elif message.is_paste():
                body = self.MESSAGES["paste"].format(message=body)
            elif message.is_upload():
                body = self._displayUpload(view, message)
            else:
                body = self._autoLink(body)

            created = QtCore.QDateTime(
                message.created_at.year,
                message.created_at.month,
                message.created_at.day,
                message.created_at.hour,
                message.created_at.minute,
                message.created_at.second
            )
            created.setTimeSpec(QtCore.Qt.UTC)

            createdFormat = "h:mm ap"
            if created.daysTo(QtCore.QDateTime.currentDateTime()):
                createdFormat = "MMM d,  {createdFormat}".format(createdFormat=createdFormat)

            key = "message"
            if message.is_by_current_user():
                key = "message_self"
            elif alert:
                key = "alert"

            html = self.MESSAGES[key].format(
                time = created.toLocalTime().toString(createdFormat),
                user = message.user.name,
                message = body
            )
        elif message.is_topic_change():
            html = self.MESSAGES["topic"].format(user=message.user.name, topic=message.body)

        if html:
            currentScrollbarValue = frame.scrollPosition()
            autoScroll = (currentScrollbarValue == frame.scrollBarMaximum(QtCore.Qt.Vertical))
            frame.setHtml(frame.toHtml() + html)
            view.show()
            if autoScroll:
                frame.scroll(0, frame.scrollBarMaximum(QtCore.Qt.Vertical))
            else:
                frame.scroll(currentScrollbarValue.x(), currentScrollbarValue.y())

            tabIndex = self._rooms[room.id]["tab"]
            tabBar = self._tabs.tabBar()
            isActiveTab = (self.isActiveWindow() and tabIndex == self._tabs.currentIndex())
            
            if message.is_text() and not isActiveTab:
                self._rooms[room.id]["newMessages"] += 1

            if self._rooms[room.id]["newMessages"] > 0:
                tabBar.setTabText(tabIndex, "{room} ({count})".format(room = room.name, count = self._rooms[room.id]["newMessages"]))

            if not isActiveTab and (alert or self._rooms[room.id]["newMessages"] > 0) and tabBar.tabTextColor(tabIndex) == self.COLORS["normal"]:
                tabBar.setTabTextColor(tabIndex, self.COLORS["alert" if alert else "new"])

            notifyInactiveTab = self.getSetting("alerts", "notify_inactive_tab")

            if not isActiveTab and (alert or notifyInactiveTab):
                self._trayIcon.alert()

            if (alert and notify) or (not isActiveTab and notifyInactiveTab and message.is_text()):
                self._notify(room, "{} says: {}".format(message.user.name, message.body))

        if updateRoom:
            if (message.is_joining() or message.is_leaving()):
                self.updateRoomUsers(room.id)
            elif message.is_upload():
                self.updateRoomUploads(room.id)
            elif message.is_topic_change() and not message.is_by_current_user():
                self._cfTopicChanged(room, message.body)

        if live and alertIsDirectPing and self.getSetting("program", "away") and self._idle:
            self._getWorker().speak(room, unicode("{user}: {message}").format(
                user = message.user.name,
                message = self.getSetting("program", "away_message")
            ))

    def _displayUpload(self, view, message):
        image = None
        if message.upload['content_type'].startswith("image/"):
            try:
                request = urllib2.Request(message.upload['url'])
                auth_header = base64.encodestring('{}:{}'.format(self._worker.getApiToken(), 'X')).replace('\n', '')
                request.add_header("Authorization", "Basic {}".format(auth_header))
                image = urllib2.urlopen(request).read()
            except:
                pass

        if image:
            width = None
            try:
                imageFile = tempfile.NamedTemporaryFile('w+b')
                imageFile.write(image)
                (width, height) = Image.open(imageFile.name).size
                imageFile.close()

                maximumImageWidth = int(view.size().width() * 0.7) # 70% of viewport
                if width > maximumImageWidth:
                    width = maximumImageWidth
            except:
                pass

            html = self.MESSAGES["image"].format(
                type = message.upload['content_type'],
                data = base64.encodestring(image),
                url = message.upload['url'],
                name = message.upload['name'],
                attribs = "width=\"{width}\" ".format(width=width) if width else ""
            )
        else:
            html = self.MESSAGES["upload"].format(
                url = message.upload['url'],
                name = message.upload['name']
            )
        return html
        
    def _matchesAlert(self, message):
        matches = False
        regexes = []
        words = self.getSetting("alerts", "matches").split(";")

        for word in words:
            regexes.append("\\b{word}\\b".format(word=word))

        for regex in regexes:
            if QtCore.QString(message).contains(QtCore.QRegExp(regex, QtCore.Qt.CaseInsensitive)):
                matches = True
                break
        return matches

    def _cfConnected(self, user, rooms):
        self._connecting = False
        self._connected = True
        self._rooms = {}

        self._toolBar["rooms"].clear()
        for room in rooms:
            self._toolBar["rooms"].addItem(room["name"], room)

        self.statusBar().showMessage(self._("{user} connected to Campfire").format(user=user.name), 5000)
        self._updateLayout()

        if not self._pingTimer:
            self._pingTimer = QtCore.QTimer(self)
            self.connect(self._pingTimer, QtCore.SIGNAL("timeout()"), self.ping)
        self._pingTimer.start(60000) # Ping every minute

        if self.getSetting("program", "away"):
            self._setUpIdleTracker()

        if self.getSetting("connection", "join"):
            rooms = self.getSetting("connection", "rooms")
            if rooms:
                for roomId in rooms.split(","):
                    count = self._toolBar["rooms"].count()
                    if count:
                        roomIndex = None
                        for i in range(count):
                            data = self._toolBar["rooms"].itemData(i)
                            if not data.isNull():
                                data = data.toMap()
                                for key in data:
                                    if str(key) == "id" and str(data[key].toString()) == roomId:
                                        roomIndex = i
                                        break;
                                if roomIndex is not None:
                                    break
                        if roomIndex is not None:
                            self.joinRoom(roomIndex)

    def _cfDisconnected(self):
        if self._pingTimer:
            self._pingTimer.stop()
            self._pingTimer = None

        if self._idleTimer:
            self._setUpIdleTracker(False)

        self._connecting = False
        self._connected = False
        self._rooms = {}
        self._worker = None
        self.statusBar().clearMessage()

    def _cfRoomJoined(self, room, messages=[], rejoined=False):
        if room.id not in self._rooms:
            return

        if not rejoined:
            self._rooms[room.id].update(self._setupRoomUI(room))
            self._rooms[room.id]["room"] = room
        self._rooms[room.id]["stream"] = self._worker.getStream(room)
        self.updateRoomUsers(room.id)
        self.updateRoomUploads(room.id)
        if not rejoined:
            self.statusBar().showMessage(self._("Joined room {room}").format(room=room.name), 5000)
        self._updatedRoomsList()
        if not rejoined and messages:
            for message in messages:
                self._cfStreamMessage(room, message, live=False, updateRoom=False)

    def _cfSpoke(self, room, message):
        self._cfStreamMessage(room, message, live=False)
        self.statusBar().clearMessage()

    def _cfRoomLeft(self, room):
        if self._rooms[room.id]["stream"]:
            self._rooms[room.id]["stream"].stop().join()
        if self._rooms[room.id]["upload"]:
            self._rooms[room.id]["upload"].stop().join()

        self._tabs.removeTab(self._rooms[room.id]["tab"])
        del self._rooms[room.id]
        self.statusBar().showMessage(self._("Left room {room}").format(room=room.name), 5000)
        self._updatedRoomsList()

    def _cfRoomUsers(self, room, users, pinging=False):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        if not pinging:
            self.statusBar().clearMessage()
        self._rooms[room.id]["usersList"].clear()
        for user in users:
            item = QtGui.QListWidgetItem(user["name"])
            item.setData(QtCore.Qt.UserRole, user)
            self._rooms[room.id]["usersList"].addItem(item)

    def _cfRoomUploads(self, room, uploads):
        # We may be disconnecting while still processing the list
        if not room.id in self._rooms:
            return

        self.statusBar().clearMessage()
        label = self._rooms[room.id]["filesLabel"]
        if uploads:
            html = ""
            for upload in uploads:
                html += "{br}&bull; <a href=\"{url}\">{name}</a>".format(
                    br = "<br />" if html else "",
                    url = upload["full_url"],
                    name = upload["name"]
                )
            html = "{text}<br />{html}".format(
                text = self._("Latest uploads:"),
                html = html
            )

            label.setText(html)
            if not label.isVisible():
                label.show()
        elif label.isVisible():
            label.setText("")
            label.hide()

    def _cfUploadProgress(self, room, current, total):
        if not room.id in self._rooms:
            return
        
        progressBar = self._rooms[room.id]["uploadProgressBar"]
        if not self._rooms[room.id]["uploadWidget"].isVisible():
            self._rooms[room.id]["uploadWidget"].show()
            progressBar.setMaximum(total)

        progressBar.setValue(current)

    def _cfUploadFinished(self, room):
        if not room.id in self._rooms:
            return

        self._rooms[room.id]["upload"].join()
        self._rooms[room.id]["upload"] = None
        self._rooms[room.id]["uploadWidget"].hide()

    def _cfTopicChanged(self, room, topic):
        if not room.id in self._rooms:
            return
        
        self._rooms[room.id]["topicLabel"].setText(topic)
        self.statusBar().clearMessage()

    def _cfConnectError(self, error):
        self._cfDisconnected()
        self._updateLayout()
        self._cfError(error)

    def _cfError(self, error):
        self.statusBar().clearMessage()
        if not self._connected:
            QtGui.QMessageBox.critical(self, "Error", self._("Error while connecting: {error}".format(error = str(error))))
        else:
            QtGui.QMessageBox.critical(self, "Error", str(error))

    def _cfRoomError(self, error, room):
        self.statusBar().clearMessage()
        if isinstance(error, RuntimeError):
            (code, message) = error
            if code == 401:
                self.statusBar().showMessage(self._("Disconnected from room. Rejoining room {room}...").format(room=room.name), 5000)
                self._rooms[room.id]["stream"].stop().join()
                self._getWorker().join(room.id, True)
                return
        QtGui.QMessageBox.critical(self, "Error", str(error))

    def _roomSelected(self, index):
        self._updatedRoomsList(index)

    def _upload(self, room, path):
        self._rooms[room.id]["upload"] = self._worker.upload(room, path)
        self._updateRoomLayout()

    def _roomTabClose(self, tabIndex):
        for roomId in self._rooms:
            if self._rooms[roomId]["tab"] == tabIndex:
                self.leaveRoom(roomId)
                break

    def _roomTabFocused(self):
        tabIndex = self._tabs.currentIndex()
        if tabIndex < 0 or not self.isActiveWindow():
            return

        room = self._roomInTabIndex(tabIndex)
        if not room:
            return

        tabBar = self._tabs.tabBar()

        if self._rooms[room.id]["newMessages"] > 0:
            self._rooms[room.id]["newMessages"] = 0
            tabBar.setTabText(tabIndex, room.name)

        if tabBar.tabTextColor(tabIndex) != self.COLORS["normal"]:
            tabBar.setTabTextColor(tabIndex, self.COLORS["normal"])

        self._updateRoomLayout()

    def _roomInTabIndex(self, index):
        room = None
        for key in self._rooms:
            if self._rooms[key]["tab"] == index:
                room = self._rooms[key]["room"]
                break
        return room

    def _roomInIndex(self, index):
        room = {}
        data = self._toolBar["rooms"].itemData(index)
        if not data.isNull():
            data = data.toMap()
            for key in data:
                room[str(key)] = unicode(data[key].toString())
        return room

    def _connectWorkerSignals(self, worker):
        self.connect(worker, QtCore.SIGNAL("error(PyQt_PyObject)"), self._cfError)
        self.connect(worker, QtCore.SIGNAL("connected(PyQt_PyObject, PyQt_PyObject)"), self._cfConnected)
        self.connect(worker, QtCore.SIGNAL("connectError(PyQt_PyObject)"), self._cfConnectError)
        self.connect(worker, QtCore.SIGNAL("streamError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError)
        self.connect(worker, QtCore.SIGNAL("uploadError(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomError)
        self.connect(worker, QtCore.SIGNAL("joined(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomJoined)
        self.connect(worker, QtCore.SIGNAL("spoke(PyQt_PyObject, PyQt_PyObject)"), self._cfSpoke)
        self.connect(worker, QtCore.SIGNAL("streamMessage(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfStreamMessage)
        self.connect(worker, QtCore.SIGNAL("left(PyQt_PyObject)"), self._cfRoomLeft)
        self.connect(worker, QtCore.SIGNAL("users(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUsers)
        self.connect(worker, QtCore.SIGNAL("uploads(PyQt_PyObject, PyQt_PyObject)"), self._cfRoomUploads)
        self.connect(worker, QtCore.SIGNAL("uploadProgress(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), self._cfUploadProgress)
        self.connect(worker, QtCore.SIGNAL("uploadFinished(PyQt_PyObject)"), self._cfUploadFinished)
        self.connect(worker, QtCore.SIGNAL("topicChanged(PyQt_PyObject, PyQt_PyObject)"), self._cfTopicChanged)

    def _getWorker(self):
        if not hasattr(self, "_workers"):
            self._workers = []

        if self._workers:
            for worker in self._workers:
                if worker.isFinished():
                    return worker

        worker = copy.copy(self._worker)
        self._connectWorkerSignals(worker)
        self._workers.append(worker)
        return worker

    def _updatedRoomsList(self, index=None):
        if not index:
            index = self._toolBar["rooms"].currentIndex()

        room = self._roomInIndex(index)

        self._toolBar["join"].setEnabled(False)
        if not room or room["id"] not in self._rooms:
            self._toolBar["join"].setEnabled(True)

        centralWidget = self.centralWidget()
        if not self._tabs.count():
            centralWidget.hide()
        else:
            centralWidget.show()

    def _notify(self, room, message):
        raise NotImplementedError("_notify() must be implemented")

    def _updateRoomLayout(self):
        room = self.getCurrentRoom()
        if room:
            canUpload = not self._rooms[room.id]["upload"]
            uploadButton = self._rooms[room.id]["uploadButton"]
            if (
                (canUpload and not uploadButton.isEnabled()) or
                (not canUpload and uploadButton.isEnabled())
            ):
                uploadButton.setEnabled(canUpload)

    def _updateLayout(self):
        self._menus["file"]["connect"].setEnabled(not self._connected and self._canConnect and not self._connecting)
        self._menus["file"]["disconnect"].setEnabled(self._connected)

        roomsEmpty = self._toolBar["rooms"].count() == 1 and self._toolBar["rooms"].itemData(0).isNull()
        if not roomsEmpty and (not self._connected or not self._toolBar["rooms"].count()):
            self._toolBar["rooms"].clear()
            self._toolBar["rooms"].addItem(self._("No rooms available"))
            self._toolBar["rooms"].setEnabled(False)
        elif not roomsEmpty:
            self._toolBar["rooms"].setEnabled(True)

        self._toolBar["roomsLabel"].setEnabled(self._toolBar["rooms"].isEnabled())
        self._toolBar["join"].setEnabled(self._toolBar["rooms"].isEnabled())

    def _setupRoomUI(self, room):
        topic = room.topic if room.topic else ""
        topicLabel = ClickableQLabel(topic)
        topicLabel.setToolTip(self._("Click here to change room's topic"))
        topicLabel.setWordWrap(True)
        self.connect(topicLabel, QtCore.SIGNAL("clicked()"), self.changeTopic)

        view = SnakeFireWebView(self)
        view.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        frame = view.page().mainFrame()

        #Send all link clicks to systems web browser
        view.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks)
        def linkClicked(url): webbrowser.open(str(url.toString()))
        view.connect(view, QtCore.SIGNAL("linkClicked (const QUrl&)"), linkClicked)

        # Support auto scroll when needed
        def autoScroll(size): frame.scroll(0, size.height())
        frame.connect(frame, QtCore.SIGNAL("contentsSizeChanged (const QSize&)"), autoScroll)

        usersList = QtGui.QListWidget()

        filesLabel = QtGui.QLabel("")
        filesLabel.setOpenExternalLinks(True)
        filesLabel.setWordWrap(True)
        filesLabel.hide()

        uploadButton = QtGui.QPushButton(self._("&Upload new file"))
        self.connect(uploadButton, QtCore.SIGNAL("clicked()"), self.uploadFile)

        uploadProgressBar = QtGui.QProgressBar()
        uploadProgressLabel = QtGui.QLabel(self._("Uploading:"))

        uploadCancelButton = QtGui.QPushButton(self._("Cancel"))
        self.connect(uploadCancelButton, QtCore.SIGNAL("clicked()"), self.uploadCancel)

        uploadLayout = QtGui.QHBoxLayout()
        uploadLayout.addWidget(uploadProgressLabel)
        uploadLayout.addWidget(uploadProgressBar)
        uploadLayout.addWidget(uploadCancelButton)

        uploadWidget = QtGui.QWidget()
        uploadWidget.setLayout(uploadLayout)
        uploadWidget.hide()

        leftFrameLayout = QtGui.QVBoxLayout()
        leftFrameLayout.addWidget(topicLabel)
        leftFrameLayout.addWidget(view)
        leftFrameLayout.addWidget(uploadWidget)

        rightFrameLayout = QtGui.QVBoxLayout()
        rightFrameLayout.addWidget(QtGui.QLabel(self._("Users in room:")))
        rightFrameLayout.addWidget(usersList)
        rightFrameLayout.addWidget(filesLabel)
        rightFrameLayout.addWidget(uploadButton)
        rightFrameLayout.addStretch(1)

        leftFrame = QtGui.QWidget()
        leftFrame.setLayout(leftFrameLayout)

        rightFrame = QtGui.QWidget()
        rightFrame.setLayout(rightFrameLayout)

        splitter = QtGui.QSplitter()
        splitter.addWidget(leftFrame)
        splitter.addWidget(rightFrame)
        splitter.setSizes([splitter.size().width() * 0.75, splitter.size().width() * 0.25])

        index = self._tabs.addTab(splitter, room.name)
        self._tabs.setCurrentIndex(index)

        if not self.COLORS["normal"]:
            self.COLORS["normal"] = self._tabs.tabBar().tabTextColor(index)
        else:
            self._tabs.tabBar().setTabTextColor(index, self.COLORS["normal"])

        return {
            "tab": index,
            "view": view,
            "frame": frame,
            "usersList": usersList,
            "topicLabel": topicLabel,
            "filesLabel": filesLabel,
            "uploadButton": uploadButton,
            "uploadWidget": uploadWidget,
            "uploadProgressBar": uploadProgressBar,
            "uploadProgressLabel": uploadProgressLabel
        }

    def _setupUI(self):
        self.setWindowTitle(self.NAME)

        self._addMenu()
        self._addToolbar()

        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self.connect(self._tabs, QtCore.SIGNAL("currentChanged(int)"), self._roomTabFocused)
        self.connect(self._tabs, QtCore.SIGNAL("tabCloseRequested(int)"), self._roomTabClose)

        self._editor = QtGui.QPlainTextEdit()
        self._editor.setFixedHeight(self._editor.fontMetrics().height() * 2)
        self._editor.installEventFilter(SuggesterKeyPressEventFilter(self, Suggester(self._editor)))

        speakButton = QtGui.QPushButton(self._("&Send"))
        self.connect(speakButton, QtCore.SIGNAL('clicked()'), self.speak)

        grid = QtGui.QGridLayout()
        grid.setRowStretch(0, 1)
        grid.addWidget(self._tabs, 0, 0, 1, -1)
        grid.addWidget(self._editor, 2, 0)
        grid.addWidget(speakButton, 2, 1)

        widget = QtGui.QWidget()
        widget.setLayout(grid)
        self.setCentralWidget(widget)

        tabWidgetFocusEventFilter = TabWidgetFocusEventFilter(self)
        self.connect(tabWidgetFocusEventFilter, QtCore.SIGNAL("tabFocused()"), self._roomTabFocused)
        widget.installEventFilter(tabWidgetFocusEventFilter)

        self.centralWidget().hide()

        size = self.getSetting("window", "size")

        if not size:
            size = QtCore.QSize(640, 480)

        self.resize(size)

        position = self.getSetting("window", "position")
        if not position:
            screen = QtGui.QDesktopWidget().screenGeometry()
            position = QtCore.QPoint((screen.width()-size.width())/2, (screen.height()-size.height())/2)

        self.move(position)

        self._updateLayout()

        menu = QtGui.QMenu(self)
        menu.addAction(self._menus["file"]["connect"])
        menu.addAction(self._menus["file"]["disconnect"])
        menu.addSeparator()
        menu.addAction(self._menus["file"]["exit"])

        self._trayIcon = Systray(self._icon, self)
        self._trayIcon.setContextMenu(menu)
        self._trayIcon.setToolTip(self.DESCRIPTION)

    def _addMenu(self):
        self._menus = {
            "file": {
                "connect": self._createAction(self._("&Connect"), self.connectNow, icon="connect.png"),
                "disconnect": self._createAction(self._("&Disconnect"), self.disconnectNow, icon="disconnect.png"),
                "exit": self._createAction(self._("E&xit"), self.exit)
            },
            "settings": {
                "alerts": self._createAction(self._("&Alerts..."), self.alerts, icon="alerts.png"),
                "options": self._createAction(self._("&Options..."), self.options)
            },
            "help": {
                "about": self._createAction(self._("A&bout"))
            }
        }

        menu = self.menuBar()

        file_menu = menu.addMenu(self._("&File"))
        file_menu.addAction(self._menus["file"]["connect"])
        file_menu.addAction(self._menus["file"]["disconnect"])
        file_menu.addSeparator()
        file_menu.addAction(self._menus["file"]["exit"])

        settings_menu = menu.addMenu(self._("S&ettings"))
        settings_menu.addAction(self._menus["settings"]["alerts"])
        settings_menu.addSeparator()
        settings_menu.addAction(self._menus["settings"]["options"])

        help_menu = menu.addMenu(self._("&Help"))
        help_menu.addAction(self._menus["help"]["about"])

    def _addToolbar(self):
        self._toolBar = {
            "connect": self._menus["file"]["connect"],
            "disconnect": self._menus["file"]["disconnect"],
            "roomsLabel": QtGui.QLabel(self._("Rooms:")),
            "rooms": QtGui.QComboBox(),
            "join": self._createAction(self._("Join room"), self.joinRoom, icon="join.png"),
            "alerts": self._menus["settings"]["alerts"]
        }

        self.connect(self._toolBar["rooms"], QtCore.SIGNAL("currentIndexChanged(int)"), self._roomSelected)

        toolBar = self.toolBar()
        toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        toolBar.addAction(self._toolBar["connect"])
        toolBar.addAction(self._toolBar["disconnect"])
        toolBar.addSeparator();
        toolBar.addWidget(self._toolBar["roomsLabel"])
        toolBar.addWidget(self._toolBar["rooms"])
        toolBar.addAction(self._toolBar["join"])
        toolBar.addSeparator();
        toolBar.addAction(self._toolBar["alerts"])

    def _createAction(self, text, slot=None, shortcut=None, icon=None, 
        tip=None, checkable=False, signal="triggered()"):
        """ Create an action """
        action = QtGui.QAction(text, self) 
        if icon is not None:
            if not isinstance(icon, QtGui.QIcon):
                action.setIcon(QtGui.QIcon(":/icons/{icon}".format(icon=icon)))
            else:
                action.setIcon(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, QtCore.SIGNAL(signal), slot) 
        if checkable: 
            action.setCheckable(True)
        return action

    def _plainTextToHTML(self, string):
        return string.replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />")

    def _autoLink(self, string):
        urlre = re.compile("(\(?https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|])(\">|</a>)?")
        urls = urlre.findall(string)
        cleanUrls = []
        for url in urls:
            if url[1]:
                continue

            currentUrl = url[0]
            if currentUrl[0] == '(' and currentUrl[-1] == ')':
                currentUrl = currentUrl[1:-1]

            if currentUrl in cleanUrls:
                continue

            cleanUrls.append(currentUrl)
            string = re.sub("(?<!(=\"|\">))" + re.escape(currentUrl),
                            "<a href=\"" + currentUrl + "\">" + currentUrl + "</a>",
                            string)
        return string

    def _(self, string, module=None):
        return str(QtCore.QCoreApplication.translate(module or Snakefire.NAME, string))
Example #21
0
class ScudCloud(QtGui.QMainWindow):

    plugins = True
    debug = False
    forceClose = False
    messages = 0

    def __init__(self, parent=None, settings_path=""):
        super(ScudCloud, self).__init__(parent)
        self.setWindowTitle('ScudCloud')
        self.settings_path = settings_path
        self.notifier = Notifier(Resources.APP_NAME,
                                 Resources.get_path('scudcloud.png'))
        self.settings = QSettings(self.settings_path + '/scudcloud.cfg',
                                  QSettings.IniFormat)
        self.identifier = self.settings.value("Domain")
        if Unity is not None:
            self.launcher = Unity.LauncherEntry.get_for_desktop_id(
                "scudcloud.desktop")
        else:
            self.launcher = DummyLauncher(self)
        self.webSettings()
        self.leftPane = LeftPane(self)
        webView = Wrapper(self)
        webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
        self.stackedWidget = QtGui.QStackedWidget()
        self.stackedWidget.addWidget(webView)
        centralWidget = QtGui.QWidget(self)
        layout = QtGui.QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        layout.addWidget(self.leftPane)
        layout.addWidget(self.stackedWidget)
        centralWidget.setLayout(layout)
        self.setCentralWidget(centralWidget)
        self.addMenu()
        self.tray = Systray(self)
        self.systray(ScudCloud.minimized)
        self.installEventFilter(self)
        if self.identifier is None:
            webView.load(QtCore.QUrl(Resources.SIGNIN_URL))
        else:
            webView.load(QtCore.QUrl(self.domain()))
        webView.show()
        # Starting unread msgs counter
        self.setupTimer()

    def setupTimer(self):
        timer = QTimer(self)
        timer.timeout.connect(self.count)
        timer.setInterval(2000)
        timer.start()

    def webSettings(self):
        self.cookiesjar = PersistentCookieJar(self)
        self.zoom = self.readZoom()
        # Required by Youtube videos (HTML5 video support only on Qt5)
        QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled,
                                                   self.plugins)
        # We don't want Java
        QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled,
                                                   False)
        # We don't need History
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.PrivateBrowsingEnabled, True)
        # Required for copy and paste clipboard integration
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.JavascriptCanAccessClipboard, True)
        # Enabling Inspeclet only when --debug=True (requires more CPU usage)
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.DeveloperExtrasEnabled, self.debug)

    def toggleFullScreen(self):
        if self.isFullScreen():
            self.showMaximized()
        else:
            self.showFullScreen()

    def restore(self):
        geometry = self.settings.value("geometry")
        if geometry is not None:
            self.restoreGeometry(geometry)
        windowState = self.settings.value("windowState")
        if windowState is not None:
            self.restoreState(windowState)
        else:
            self.showMaximized()

    def systray(self, show=None):
        if show is None:
            show = self.settings.value("Systray") == "True"
        if show:
            self.tray.show()
            self.menus["file"]["close"].setEnabled(True)
            self.settings.setValue("Systray", "True")
        else:
            self.tray.setVisible(False)
            self.menus["file"]["close"].setEnabled(False)
            self.settings.setValue("Systray", "False")

    def readZoom(self):
        default = 1
        if self.settings.value("Zoom") is not None:
            default = float(self.settings.value("Zoom"))
        return default

    def setZoom(self, factor=1):
        if factor > 0:
            for i in range(0, self.stackedWidget.count()):
                widget = self.stackedWidget.widget(i)
                widget.setZoomFactor(factor)
            self.settings.setValue("Zoom", factor)

    def zoomIn(self):
        self.setZoom(self.current().zoomFactor() + 0.1)

    def zoomOut(self):
        self.setZoom(self.current().zoomFactor() - 0.1)

    def zoomReset(self):
        self.setZoom()

    def addMenu(self):
        self.menus = {
            "file": {
                "preferences":
                self.createAction("Preferences",
                                  lambda: self.current().preferences()),
                "systray":
                self.createAction("Close to Tray", self.systray, None, True),
                "addTeam":
                self.createAction("Sign in to Another Team",
                                  lambda: self.current().addTeam()),
                "signout":
                self.createAction("Signout", lambda: self.current().logout()),
                "close":
                self.createAction("Close", self.close, QKeySequence.Close),
                "exit":
                self.createAction("Quit", self.exit, QKeySequence.Quit)
            },
            "edit": {
                "undo": self.current().pageAction(QtWebKit.QWebPage.Undo),
                "redo": self.current().pageAction(QtWebKit.QWebPage.Redo),
                "cut": self.current().pageAction(QtWebKit.QWebPage.Cut),
                "copy": self.current().pageAction(QtWebKit.QWebPage.Copy),
                "paste": self.current().pageAction(QtWebKit.QWebPage.Paste),
                "back": self.current().pageAction(QtWebKit.QWebPage.Back),
                "forward":
                self.current().pageAction(QtWebKit.QWebPage.Forward),
                "reload": self.current().pageAction(QtWebKit.QWebPage.Reload)
            },
            "view": {
                "zoomin":
                self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn),
                "zoomout":
                self.createAction("Zoom Out", self.zoomOut,
                                  QKeySequence.ZoomOut),
                "reset":
                self.createAction("Reset", self.zoomReset,
                                  QtCore.Qt.CTRL + QtCore.Qt.Key_0),
                "fullscreen":
                self.createAction("Toggle Full Screen", self.toggleFullScreen,
                                  QtCore.Qt.Key_F11)
            },
            "help": {
                "help":
                self.createAction("Help and Feedback",
                                  self.current().help,
                                  QKeySequence.HelpContents),
                "center":
                self.createAction("Slack Help Center",
                                  self.current().helpCenter),
                "about":
                self.createAction("About", lambda: self.current().about())
            }
        }
        menu = self.menuBar()
        fileMenu = menu.addMenu("&File")
        fileMenu.addAction(self.menus["file"]["preferences"])
        fileMenu.addAction(self.menus["file"]["systray"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["addTeam"])
        fileMenu.addAction(self.menus["file"]["signout"])
        fileMenu.addSeparator()
        fileMenu.addAction(self.menus["file"]["close"])
        fileMenu.addAction(self.menus["file"]["exit"])
        editMenu = menu.addMenu("&Edit")
        editMenu.addAction(self.menus["edit"]["undo"])
        editMenu.addAction(self.menus["edit"]["redo"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["cut"])
        editMenu.addAction(self.menus["edit"]["copy"])
        editMenu.addAction(self.menus["edit"]["paste"])
        editMenu.addSeparator()
        editMenu.addAction(self.menus["edit"]["back"])
        editMenu.addAction(self.menus["edit"]["forward"])
        editMenu.addAction(self.menus["edit"]["reload"])
        viewMenu = menu.addMenu("&View")
        viewMenu.addAction(self.menus["view"]["zoomin"])
        viewMenu.addAction(self.menus["view"]["zoomout"])
        viewMenu.addAction(self.menus["view"]["reset"])
        viewMenu.addSeparator()
        viewMenu.addAction(self.menus["view"]["fullscreen"])
        helpMenu = menu.addMenu("&Help")
        helpMenu.addAction(self.menus["help"]["help"])
        helpMenu.addAction(self.menus["help"]["center"])
        helpMenu.addSeparator()
        helpMenu.addAction(self.menus["help"]["about"])
        self.enableMenus(False)
        showSystray = self.settings.value("Systray") == "True"
        self.menus["file"]["systray"].setChecked(showSystray)
        self.menus["file"]["close"].setEnabled(showSystray)

    def enableMenus(self, enabled):
        self.menus["file"]["preferences"].setEnabled(enabled == True)
        self.menus["file"]["addTeam"].setEnabled(enabled == True)
        self.menus["file"]["signout"].setEnabled(enabled == True)
        self.menus["help"]["help"].setEnabled(enabled == True)

    def createAction(self, text, slot, shortcut=None, checkable=False):
        action = QtGui.QAction(text, self)
        if shortcut is not None:
            action.setShortcut(shortcut)
        action.triggered.connect(slot)
        if checkable:
            action.setCheckable(True)
        return action

    def domain(self):
        if self.identifier.endswith(".slack.com"):
            return self.identifier
        else:
            return "https://" + self.identifier + ".slack.com"

    def current(self):
        return self.stackedWidget.currentWidget()

    def teams(self, teams):
        if teams is not None and len(teams) > 1:
            self.leftPane.show()
            for t in teams:
                try:
                    self.leftPane.addTeam(t['id'], t['team_name'],
                                          t['team_url'],
                                          t['team_icon']['image_88'],
                                          t == teams[0])
                except:
                    self.leftPane.addTeam(t['id'], t['team_name'],
                                          t['team_url'], '', t == teams[0])

    def switchTo(self, url):
        qUrl = QtCore.QUrl(url)
        index = -1
        for i in range(0, self.stackedWidget.count()):
            if self.stackedWidget.widget(i).url().toString().startswith(url):
                index = i
                break
        if index != -1:
            self.stackedWidget.setCurrentIndex(index)
        else:
            webView = Wrapper(self)
            webView.page().networkAccessManager().setCookieJar(self.cookiesjar)
            webView.load(qUrl)
            webView.show()
            self.stackedWidget.addWidget(webView)
            self.stackedWidget.setCurrentWidget(webView)
        self.quicklist(self.current().listChannels())
        self.enableMenus(self.current().isConnected())
        # Save the last used team as default
        self.settings.setValue("Domain", 'https://' + qUrl.host())

    def eventFilter(self, obj, event):
        if event.type(
        ) == QtCore.QEvent.ActivationChange and self.isActiveWindow():
            self.focusInEvent(event)
        if event.type() == QtCore.QEvent.KeyPress:
            # Ctrl + <n>
            if QtGui.QApplication.keyboardModifiers(
            ) == QtCore.Qt.ControlModifier:
                if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0)
                elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1)
                elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2)
                elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3)
                elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4)
                elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5)
                elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6)
                elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7)
                elif event.key() == QtCore.Qt.Key_9:
                    self.leftPane.click(8)
                    # ctrl + tab
                elif event.key() == QtCore.Qt.Key_Tab:
                    self.leftPane.clickNext(1)

            # ctrl + backtab
            if (QtGui.QApplication.keyboardModifiers()
                    & QtCore.Qt.ControlModifier) and (
                        QtGui.QApplication.keyboardModifiers()
                        & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_Backtab:
                    self.leftPane.clickNext(-1)

            # Ctrl + Shift + <key>
            if (QtGui.QApplication.keyboardModifiers()
                    & QtCore.Qt.ShiftModifier) and (
                        QtGui.QApplication.keyboardModifiers()
                        & QtCore.Qt.ShiftModifier):
                if event.key() == QtCore.Qt.Key_V:
                    self.current().createSnippet()
        return QtGui.QMainWindow.eventFilter(self, obj, event)

    def focusInEvent(self, event):
        self.launcher.set_property("urgent", False)
        self.tray.stopAlert()

    def titleChanged(self):
        self.setWindowTitle(self.current().title())

    def closeEvent(self, event):
        if not self.forceClose and self.settings.value("Systray") == "True":
            self.hide()
            event.ignore()
        else:
            self.cookiesjar.save()
            self.settings.setValue("geometry", self.saveGeometry())
            self.settings.setValue("windowState", self.saveState())

    def show(self):
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized
                            | QtCore.Qt.WindowActive)
        self.activateWindow()
        self.setVisible(True)

    def exit(self):
        self.forceClose = True
        self.close()

    def quicklist(self, channels):
        if Dbusmenu is not None:
            ql = Dbusmenu.Menuitem.new()
            self.launcher.set_property("quicklist", ql)
            if channels is not None:
                for c in channels:
                    if c['is_member']:
                        item = Dbusmenu.Menuitem.new()
                        item.property_set(Dbusmenu.MENUITEM_PROP_LABEL,
                                          "#" + c['name'])
                        item.property_set("id", c['name'])
                        item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE,
                                               True)
                        item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
                                     self.current().openChannel)
                        ql.child_append(item)
                self.launcher.set_property("quicklist", ql)

    def notify(self, title, message):
        self.notifier.notify(title, message)
        self.alert()

    def alert(self):
        if not self.isActiveWindow():
            self.launcher.set_property("urgent", True)
            self.tray.alert()

    def count(self):
        total = 0
        for i in range(0, self.stackedWidget.count()):
            widget = self.stackedWidget.widget(i)
            widget.count()
            if widget.messages == 0:
                self.leftPane.stopAlert(widget.team())
            else:
                self.leftPane.alert(widget.team())
            if widget.messages is not None:
                total += widget.messages
        if total > self.messages:
            self.alert()
        if 0 == total:
            self.launcher.set_property("count_visible", False)
            self.tray.setCounter(0)
        else:
            self.tray.setCounter(total)
            self.launcher.set_property("count", total)
            self.launcher.set_property("count_visible", True)
        self.messages = total