diff --git a/qweechat/buffer.py b/qweechat/buffer.py index 81aa4b7..4da5f89 100644 --- a/qweechat/buffer.py +++ b/qweechat/buffer.py @@ -149,9 +149,10 @@ class Buffer(QtCore.QObject): bufferInput = qt_compat.Signal(str, str) - def __init__(self, data={}): + def __init__(self, data={}, config=False): QtCore.QObject.__init__(self) self.data = data + self.config = config self.nicklist = {} self.widget = BufferWidget(display_nicklist=self.data.get('nicklist', 0)) @@ -183,6 +184,14 @@ class Buffer(QtCore.QObject): if self.data: self.bufferInput.emit(self.data['full_name'], text) + def update_config(self): + """Match visibility to configuration, faster than a nicklist refresh""" + if (self.config): + nicklist_visible = self.config.get("look", "nicklist") != "off" + topic_visible = self.config.get("look", "topic") != "off" + self.widget.nicklist.setVisible(nicklist_visible) + self.widget.title.setVisible(topic_visible) + def nicklist_add_item(self, parent, group, prefix, name, visible): """Add a group/nick in nicklist.""" if group: @@ -245,4 +254,8 @@ class Buffer(QtCore.QObject): icon = QtGui.QIcon(pixmap) item = QtGui.QListWidgetItem(icon, nick['name']) self.widget.nicklist.addItem(item) - self.widget.nicklist.setVisible(True) + if self.config and self.config.get("look", + "nicklist") == "off": + self.widget.nicklist.setVisible(False) + else: + self.widget.nicklist.setVisible(True) diff --git a/qweechat/config.py b/qweechat/config.py index 1613860..df939fc 100644 --- a/qweechat/config.py +++ b/qweechat/config.py @@ -36,6 +36,12 @@ CONFIG_DEFAULT_OPTIONS = (('relay.server', ''), ('relay.autoconnect', 'off'), ('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)), ('look.debug', 'off'), + ('look.style', ''), + ('look.buffer_list', 'left'), + ('look.nicklist', 'on'), + ('look.toolbar', 'on'), + ('look.menubar', 'on'), + ('look.topic', 'on'), ('look.statusbar', 'off')) # Default colors for WeeChat color options (option name, #rgb value) diff --git a/qweechat/preferences.py b/qweechat/preferences.py new file mode 100644 index 0000000..043e19d --- /dev/null +++ b/qweechat/preferences.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +# +# preferences.py - preferences dialog box +# +# Copyright (C) 2016 Ricky Brent +# +# This file is part of QWeeChat, a Qt remote GUI for WeeChat. +# +# QWeeChat is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# QWeeChat is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QWeeChat. If not, see . +# + +import qt_compat +import config + +QtCore = qt_compat.import_module('QtCore') +QtGui = qt_compat.import_module('QtGui') + + +class PreferencesDialog(QtGui.QDialog): + """Preferences dialog.""" + + def __init__(self, name, parent, *args): + QtGui.QDialog.__init__(*(self,) + args) + self.setModal(True) + self.setWindowTitle(name) + self.parent = parent + self.config = parent.config + self.stacked_panes = QtGui.QStackedWidget() + self.list_panes = PreferencesTreeWidget("Settings") + + splitter = QtGui.QSplitter() + splitter.addWidget(self.list_panes) + splitter.addWidget(self.stacked_panes) + + # Follow same order as defaults: + section_panes = {} + for section in self.config.sections(): + item = QtGui.QTreeWidgetItem(section) + item.setText(0, section) + section_panes[section] = PreferencesPaneWidget(section) + self.list_panes.addTopLevelItem(item) + self.stacked_panes.addWidget(section_panes[section]) + + for setting, default in config.CONFIG_DEFAULT_OPTIONS: + section, key = setting.split(".") + section_panes[section].addItem(key, self.config.get(section, key)) + for key, value in self.config.items("color"): + section_panes["color"].addItem(key, value) + + self.list_panes.currentItemChanged.connect(self._pane_switch) + self.list_panes.setCurrentItem(self.list_panes.topLevelItem(0)) + + hbox = QtGui.QHBoxLayout() + self.dialog_buttons = QtGui.QDialogButtonBox() + self.dialog_buttons.setStandardButtons( + QtGui.QDialogButtonBox.Save | QtGui.QDialogButtonBox.Cancel) + self.dialog_buttons.rejected.connect(self.close) + self.dialog_buttons.accepted.connect(self._save_and_close) + + hbox.addStretch(1) + hbox.addWidget(self.dialog_buttons) + hbox.addStretch(1) + + vbox = QtGui.QVBoxLayout() + vbox.addWidget(splitter) + vbox.addLayout(hbox) + + self.setLayout(vbox) + self.show() + + def _pane_switch(self, item): + """Switch the visible preference pane.""" + index = self.list_panes.indexOfTopLevelItem(item) + if index >= 0: + self.stacked_panes.setCurrentIndex(index) + + def _save_and_close(self): + for widget in (self.stacked_panes.widget(i) + for i in range(self.stacked_panes.count())): + for key, field in widget.fields.items(): + if isinstance(field, QtGui.QComboBox): + text = field.itemText(field.currentIndex()) + elif isinstance(field, QtGui.QCheckBox): + text = "on" if field.isChecked() else "off" + else: + text = field.text() + self.config.set(widget.section_name, key, str(text)) + config.write(self.config) + self.parent.apply_preferences() + self.close() + + +class PreferencesTreeWidget(QtGui.QTreeWidget): + """Widget with tree list of preferences.""" + + def __init__(self, header_label, *args): + QtGui.QTreeWidget.__init__(*(self,) + args) + self.setHeaderLabel(header_label) + self.setRootIsDecorated(False) + self.setMaximumWidth(90) + self.setTextElideMode(QtCore.Qt.ElideNone) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setFocusPolicy(QtCore.Qt.NoFocus) + + +class PreferencesColorEdit(QtGui.QPushButton): + """Simple color square that changes based on the color selected.""" + def __init__(self, *args): + QtGui.QPushButton.__init__(*(self,) + args) + self.color = "#000000" + self.clicked.connect(self._color_picker) + # Some of the configured colors use a astrisk prefix. + # Toggle this on right click. + self.star = False + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.customContextMenuRequested.connect(self._color_star) + + def insert(self, color): + """Insert the desired color for the widget.""" + if color[:1] == "*": + self.star = True + color = color[1:] + self.setText("*" if self.star else "") + self.color = color + self.setStyleSheet("background-color: " + color) + + def text(self): + """Returns the hex value of the color.""" + return ("*" if self.star else "") + self.color + + def _color_picker(self): + color = QtGui.QColorDialog.getColor() + self.insert(color.name()) + + def _color_star(self): + self.star = not self.star + self.insert(self.text()) + + +class PreferencesPaneWidget(QtGui.QWidget): + """ + Widget with (from top to bottom): + title, chat + nicklist (optional) + prompt/input. + """ + + def __init__(self, section_name): + QtGui.QWidget.__init__(self) + self.grid = QtGui.QGridLayout() + self.grid.setAlignment(QtCore.Qt.AlignTop) + self.section_name = section_name + self.fields = {} + self.setLayout(self.grid) + self.grid.setColumnStretch(2, 1) + self.grid.setSpacing(10) + self.checkboxes = ("ssl", "autoconnect", "statusbar", "topic", + "menubar", "toolbar", "nicklist", "debug") + self.comboboxes = {"style": QtGui.QStyleFactory.keys(), + "buffer_list": ["left", "right"]} + + def addItem(self, key, value): + """Add a key-value pair.""" + line = len(self.fields) + name = key.capitalize().replace("_", " ") + start = 0 + if self.section_name == "color": + start = 2 * (line % 2) + line = line // 2 + self.grid.addWidget(QtGui.QLabel(name), line, start + 0) + if self.section_name == "color": + edit = PreferencesColorEdit() + edit.setFixedWidth(edit.sizeHint().height()) + edit.insert(value) + elif key in self.comboboxes.keys(): + edit = QtGui.QComboBox() + edit.addItems(self.comboboxes[key]) + edit.setCurrentIndex(edit.findText(value)) + edit.setFixedWidth(200) + elif key in self.checkboxes: + edit = QtGui.QCheckBox() + edit.setChecked(value == "on") + else: + edit = QtGui.QLineEdit() + edit.setFixedWidth(200) + edit.insert(value) + if key == 'password': + edit.setEchoMode(QtGui.QLineEdit.Password) + self.grid.addWidget(edit, line, start + 1) + self.fields[key] = edit diff --git a/qweechat/qt_compat.py b/qweechat/qt_compat.py index 8940288..aa3a2e5 100644 --- a/qweechat/qt_compat.py +++ b/qweechat/qt_compat.py @@ -29,7 +29,10 @@ except ImportError: def _pyside_import_module(moduleName): pyside = __import__('PySide', globals(), locals(), [moduleName], -1) - return getattr(pyside, moduleName) + mod = getattr(pyside, moduleName) + if moduleName == "QtGui": + mod.QWIDGETSIZE_MAX = ((1 << 24) - 1) + return mod def _pyqt4_import_module(moduleName): diff --git a/qweechat/qweechat.py b/qweechat/qweechat.py index 49e6b91..179a520 100644 --- a/qweechat/qweechat.py +++ b/qweechat/qweechat.py @@ -44,6 +44,7 @@ from connection import ConnectionDialog from buffer import BufferListWidget, Buffer from debug import DebugDialog from about import AboutDialog +from preferences import PreferencesDialog from version import qweechat_version QtCore = qt_compat.import_module('QtCore') @@ -91,14 +92,11 @@ class MainWindow(QtGui.QMainWindow): self.stacked_buffers.addWidget(self.buffers[0].widget) # splitter with buffers + chat/input - splitter = QtGui.QSplitter() - splitter.addWidget(self.list_buffers) - splitter.addWidget(self.stacked_buffers) + self.splitter = QtGui.QSplitter() + self.splitter.addWidget(self.list_buffers) + self.splitter.addWidget(self.stacked_buffers) - self.setCentralWidget(splitter) - - if self.config.getboolean('look', 'statusbar'): - self.statusBar().visible = True + self.setCentralWidget(self.splitter) # actions for menu and toolbar actions_def = { @@ -124,6 +122,27 @@ class MainWindow(QtGui.QMainWindow): 'application-exit.png', 'Quit application', 'Ctrl+Q', self.close], } + # toggleable actions + self.toggles_def = { + 'show menubar': [ + 'look.menubar', 'Show Menubar', + 'Ctrl+M', self.toggle_menubar], + 'show toolbar': [ + 'look.toolbar', 'Show Toolbar', + False, self.toggle_toolbar], + 'show status bar': [ + 'look.statusbar', 'Show Status Bar', + False, self.toggle_statusbar], + 'show topic': [ + 'look.topic', 'Show Topic', + False, self.toggle_topic], + 'show nick list': [ + 'look.nicklist', 'Show Nick List', + 'Ctrl+F7', self.toggle_nicklist], + 'fullscreen': [ + False, 'Fullscreen', + 'F11', self.toggle_fullscreen], + } self.actions = {} for name, action in list(actions_def.items()): self.actions[name] = QtGui.QAction( @@ -133,6 +152,13 @@ class MainWindow(QtGui.QMainWindow): self.actions[name].setStatusTip(action[1]) self.actions[name].setShortcut(action[2]) self.actions[name].triggered.connect(action[3]) + for name, action in list(self.toggles_def.items()): + self.actions[name] = QtGui.QAction(name.capitalize(), self) + self.actions[name].setStatusTip(action[1]) + self.actions[name].setCheckable(True) + if action[2]: + self.actions[name].setShortcut(action[2]) + self.actions[name].triggered.connect(action[3]) # menu self.menu = self.menuBar() @@ -142,6 +168,15 @@ class MainWindow(QtGui.QMainWindow): self.actions['preferences'], self.actions['save connection'], self.actions['quit']]) + menu_view = self.menu.addMenu('&View') + menu_view.addActions([self.actions['show menubar'], + self.actions['show toolbar'], + self.actions['show status bar'], + self._actions_separator(), + self.actions['show topic'], + self.actions['show nick list'], + self._actions_separator(), + self.actions['fullscreen']]) menu_window = self.menu.addMenu('&Window') menu_window.addAction(self.actions['debug']) menu_help = self.menu.addMenu('&Help') @@ -159,12 +194,20 @@ class MainWindow(QtGui.QMainWindow): # toolbar toolbar = self.addToolBar('toolBar') toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) + toolbar.setMovable(False) toolbar.addActions([self.actions['connect'], self.actions['disconnect'], self.actions['debug'], self.actions['preferences'], self.actions['about'], self.actions['quit']]) + self.toolbar = toolbar + + # Override context menu for both -- default is a simple menubar toggle. + self.menu.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.toolbar.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.menu.customContextMenuRequested.connect(self._menu_context) + self.toolbar.customContextMenuRequested.connect(self._menu_context) self.buffers[0].widget.input.setFocus() @@ -180,9 +223,60 @@ class MainWindow(QtGui.QMainWindow): 'ssl'), self.config.get('relay', 'password'), self.config.get('relay', 'lines')) + self.apply_preferences() self.show() + def _actions_separator(self): + """Create a new QAction separator.""" + sep = QtGui.QAction("", self) + sep.setSeparator(True) + return sep + + def apply_preferences(self): + """Apply non-server options from preferences.""" + app = QtCore.QCoreApplication.instance() + if self.config.getboolean('look', 'toolbar'): + self.toolbar.show() + else: + self.toolbar.hide() + # Change the height to avoid losing all hotkeys: + if self.config.getboolean('look', 'menubar'): + self.menu.setMaximumHeight(QtGui.QWIDGETSIZE_MAX) + else: + self.menu.setFixedHeight(1) + # Apply the selected qt style here so it will update without a restart + if self.config.get('look', 'style'): + app.setStyle(QtGui.QStyleFactory.create( + self.config.get('look', 'style'))) + # Statusbar: + if self.config.getboolean('look', 'statusbar'): + self.statusBar().show() + else: + self.statusBar().hide() + # Move the buffer list / main buffer view: + if self.config.get('look', 'buffer_list') == 'right': + self.splitter.insertWidget(1, self.list_buffers) + else: + self.splitter.insertWidget(1, self.stacked_buffers) + # Update visibility of all nicklists/topics: + for buffer in self.buffers: + buffer.update_config() + # Update toggle state for menubar: + for name, action in list(self.toggles_def.items()): + if action[0]: + ac = action[0].split(".") + toggle = self.config.get(ac[0], ac[1]) + self.actions[name].setChecked(toggle == "on") + + def _menu_context(self, event): + """Show a slightly nicer context menu for the menu/toolbar.""" + menu = QtGui.QMenu() + menu.addActions([self.actions['show menubar'], + self.actions['show toolbar'], + self.actions['show status bar']]) + menu.exec_(self.mapToGlobal(event)) + def _buffer_switch(self, index): """Switch to a buffer.""" if index >= 0: @@ -198,10 +292,7 @@ class MainWindow(QtGui.QMainWindow): def open_preferences_dialog(self): """Open a dialog with preferences.""" - # TODO: implement the preferences dialog box - messages = ['Not yet implemented!', - ''] - self.preferences_dialog = AboutDialog('Preferences', messages, self) + self.preferences_dialog = PreferencesDialog('Preferences', self) def save_connection(self): """Save connection configuration.""" @@ -266,6 +357,39 @@ class MainWindow(QtGui.QMainWindow): self.connection_dialog.dialog_buttons.accepted.connect( self.connect_weechat) + def toggle_setting(self, section, option): + """Toggles any boolean setting.""" + val = self.config.getboolean(section, option) + self.config.set(section, option, "off" if val else "on") + self.apply_preferences() + + def toggle_menubar(self): + """Toggle menubar.""" + self.toggle_setting('look', 'menubar') + + def toggle_toolbar(self): + """Toggle toolbar.""" + self.toggle_setting('look', 'toolbar') + + def toggle_statusbar(self): + """Toggle statusbar.""" + self.toggle_setting('look', 'statusbar') + + def toggle_topic(self): + """Toggle topic.""" + self.toggle_setting('look', 'topic') + + def toggle_nicklist(self): + """Toggle nicklist.""" + self.toggle_setting('look', 'nicklist') + + def toggle_fullscreen(self): + """Toggle fullscreen.""" + if self.isFullScreen(): + self.showNormal() + else: + self.showFullScreen() + def connect_weechat(self): """Connect to WeeChat.""" self.network.connect_weechat( @@ -498,7 +622,7 @@ class MainWindow(QtGui.QMainWindow): def create_buffer(self, item): """Create a new buffer.""" - buf = Buffer(item) + buf = Buffer(item, self.config) buf.bufferInput.connect(self.buffer_input) buf.widget.input.bufferSwitchPrev.connect( self.list_buffers.switch_prev_buffer)