Compare commits

...

24 Commits

Author SHA1 Message Date
fcee502eb9 update 2024-03-08 04:24:04 +00:00
1e4097880a update 2024-02-21 08:06:56 +00:00
55e7da35f4 update 2024-02-20 01:41:18 +00:00
5e99216cf2 add some preferences 2024-02-18 19:23:42 +00:00
1555e20459 update 2024-02-17 20:23:53 +00:00
9390c9bb91 qweechat/__main__.py 2024-02-17 20:08:31 +00:00
1a71f78521 fix icons 2024-02-13 23:04:44 +00:00
a23eb74aa8 fixes 2024-02-13 22:48:08 +00:00
38d82dfc82 pyproject.toml 2024-02-13 18:59:07 +00:00
60c48bcb6a new 2024-02-11 07:50:55 +00:00
c7580c3a35 qt6 fixes 2024-02-10 06:29:40 +00:00
e29185c293 qt6 fixes 2024-02-09 09:46:50 +00:00
acb9e83c29 qtpy 2024-02-08 20:37:58 +00:00
b54c0d1716 update 2023-12-18 06:45:33 +00:00
b1f0ad4bd8 README.md 2023-12-12 06:09:47 +00:00
ba73b0ac36 Convert to PyQt5 2022-11-20 03:41:26 +00:00
82e0d92056 Update copyright dates 2022-01-12 23:52:09 +01:00
001244e9ab Add tests with Python 3.10 2021-12-02 08:17:39 +01:00
99fe78515c Rename default option "server" to "hostname" 2021-11-14 19:53:15 +01:00
2a814055fe Ignore bandit security error about possible hardcoded password
Bandit reports this error:

>> Issue: [B105:hardcoded_password_string] Possible hardcoded password: 'init password=%(password)s%(totp)s

The password is of course never hardcoded there.
2021-11-14 19:00:55 +01:00
0dffebcf24 Remove unused import 2021-11-14 18:57:25 +01:00
0feac51b53 Rename variable _PROTO_SYNC to _PROTO_SYNC_CMDS 2021-11-14 18:56:48 +01:00
ae648fd7f6 Rename option/field "server" to "hostname" 2021-11-14 18:54:48 +01:00
c05ab0e534 Replace freenode by libera 2021-11-14 18:46:47 +01:00
45 changed files with 1600 additions and 170 deletions

View File

@ -14,6 +14,7 @@ jobs:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-20.04

7
.rsync.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/sh
# find * -name \*.py | xargs grep -l '[ ]*$' | xargs sed -i -e 's/[ ]*$//'
rsync "$@" -vax --include \*.py --exclude \*.log --exclude \*.out \
--exclude \*.egg-info --exclude libs --exclude dist --exclude build \
--exclude \*.pyc --exclude .pyl\* --exclude \*~ --exclude \*.so \
./ ../qweechat.git/|grep -v /$

View File

@ -20,5 +20,5 @@ Alphabetically:
Developers are connected to IRC:
* server: 'irc.freenode.net'
* channels: '#weechat' (English) and '#weechat-fr' (French)
* server: irc.libera.chat
* channels: #weechat (English) and #weechat-fr (French)

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2021-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -16,12 +16,23 @@
# You should have received a copy of the GNU General Public License
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
#
# to run the tests, run make PASS=controllerpassword test
PREFIX=/usr/local
PYTHON_EXE_MSYS=${PREFIX}/bin/python3.sh
PIP_EXE_MSYS=${PREFIX}/bin/pip3.sh
LOCAL_DOCTEST=${PREFIX}/bin/toxcore_run_doctest3.bash
DOCTEST=${LOCAL_DOCTEST}
PYTHON_MINOR=`python3 --version 2>&1 | sed -e 's@^.* @@' -e 's@\.[0-9]*$$@@'`
MOD=qweechat
all: check
check: lint test
check::
sh ${PYTHON_EXE_MSYS} -c "import ${MOD}"
sh ${PYTHON_EXE_MSYS} -c "from ${MOD} import config"
lint: flake8 pylint bandit
lint:: flake8 pylint bandit
flake8:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
@ -32,3 +43,29 @@ pylint:
bandit:
bandit -r qweechat
rsync::
bash .rsync.sh
install:: install-pip
install-setup::
# deprecated
${PYTHON_EXE_MSYS} -W ignore::DeprecationWarning \
setup.py install \
--prefix ${PREFIX}
install-pip::
# we install --nodeps because pip is installing stuff we already have in the OS
${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \
--no-deps \
--target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \
--upgrade .
sed -i -e "1s@/usr/bin/python${PYTHON_MINOR}@${PYTHON_EXE_MSYS}@" \
${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/bin/*
veryclean:: clean
rm -rf build dist $(MOD).egg-info
clean::
find . -type f -name \*~ -delete

View File

@ -20,29 +20,41 @@ Homepage: https://weechat.org/
QWeeChat requires:
- Python ≥ 3.7
- [PySide6](https://pypi.org/project/PySide6/)
- [WeeChat](https://weechat.org) ≥ 0.3.7, on local or remote machine, with relay plugin enabled and listening on a port with protocol "weechat"
- PyQt5 or PyQt5 and maybe PySide2 and PySide2 using qtpy
- dev-python/qtconsole; the requirements.txt file says >= 5.5.1 but earlier versions may work just fine
- [WeeChat](https://weechat.org) ≥ 0.3.7, on local or remote machine, with relay plugin enabled and listening on a port with protocol "weechat" Tested with 4.1.2
### Install via source distribution
Look at the Makefile and customize the variables; then
```
$ pip install .
$ make install
```
## WeeChat setup
You have to add a relay port in WeeChat, for example on port 1234:
You have to add a relay port in WeeChat.
Follow the normal instructions for adding a ```relay``` to
[weechat](https://github.com/weechat/weechat)
```
/set relay.network.password "mypass"
/relay add weechat 1234
/relay add weechat 9000
/relay start weechat
```
or
```
weechat -r '/relay add weechat 9000;/relay start weechat'
```
and run qweechat either under toxygen, or standalone
```
python3 -m qweechat
```
## Connect to WeeChat
In QWeeChat, click on connect and enter fields:
- `server`: the IP address or hostname of your machine with WeeChat running
- `hostname`: the IP address or hostname of your machine with WeeChat running
- `port`: the relay port (defined in WeeChat)
- `password`: the relay password (defined in WeeChat)
- `totp`: the Time-Based One-Time Password (optional, to set if required by WeeChat)
@ -51,7 +63,7 @@ Options can be changed in file `~/.config/qweechat/qweechat.conf`.
## Copyright
Copyright © 2011-2021 [Sébastien Helleu](https://github.com/flashcode)
Copyright © 2011-2022 [Sébastien Helleu](https://github.com/flashcode)
This file is part of QWeeChat, a Qt remote GUI for WeeChat.
@ -67,3 +79,11 @@ 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 <https://www.gnu.org/licenses/>.
## Updates
Up-to-date code is on https://git.plastiras.org/emdee/qweechat
You can also run this qweechat under toxygen
https://git.macaw.me/emdee/toxygen
For tox in weechat, see https://git.macaw.me/emdee/tox-weechat

52
pyproject.toml Normal file
View File

@ -0,0 +1,52 @@
[project]
name = 'qweechat'
requires-python = ">= 3.7"
description = "qtpy channel for qweechat"
keywords = ["qt", "console", "weechat"]
classifiers = [
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 4 - Beta",
# Indicate who your project is intended for
"Intended Audience :: Developers",
# Specify the Python versions you support here.
"Programming Language :: Python :: 3",
"License :: OSI Approved",
"Operating System :: POSIX :: BSD :: FreeBSD",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
]
dynamic = ["version", "readme", "dependencies"] # cannot be dynamic ['license']
[project.license]
file = "COPYING"
[project.urls]
repository = "https://git.plastiras.org/emdee/qweechat"
homepage = "https://git.plastiras.org/emdee/qweechat"
[build-system]
requires = ["setuptools>=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"
# backend = "setuptools.build_meta:__legacy__"
[tool.setuptools.dynamic]
version = {attr = "qweechat.version.VERSION"}
readme = {file = ["README.md"]}
dependencies = {file = ["requirements.txt"]}
#[tool.setuptools]
#packages = ["qweechat", "qweechat.weechat"]
#[tool.setuptools.packages.find]
#where=src

View File

@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
#
# preferences.py - preferences dialog box
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
#
# 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 <http://www.gnu.org/licenses/>.
#
"""Preferences dialog box."""
from PySide6 import QtCore, QtWidgets as QtGui
class PreferencesDialog(QtGui.QDialog):
"""Preferences dialog."""
def __init__(self, *args):
QtGui.QDialog.__init__(*(self,) + args)
self.setModal(True)
self.setWindowTitle('Preferences')
close_button = QtGui.QPushButton('Close')
close_button.pressed.connect(self.close)
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(close_button)
hbox.addStretch(1)
vbox = QtGui.QVBoxLayout()
label = QtGui.QLabel('Not yet implemented!')
label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label)
label = QtGui.QLabel('')
label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.show()

View File

@ -1 +1,4 @@
PySide6
# works with PyQt5 and PyQt6 and maybe PySide2 and PySide6 as well
PyQt5
# earlier versions may work just fine
pyqtconsole >= 5.5.1

62
setup.cfg Normal file
View File

@ -0,0 +1,62 @@
[metadata]
classifiers =
License :: OSI Approved
License :: OSI Approved :: BSD 1-clause
Intended Audience :: Web Developers
Operating System :: Microsoft :: Windows
Operating System :: POSIX :: BSD :: FreeBSD
Operating System :: POSIX :: Linux
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: Implementation :: CPython
[options]
zip_safe = false
python_requires = >= 3.7
package_dir=
=src
# find:
packages =
qweechat
qweechat.weechat
[options.package_data]
qweechat =
data/icons/*.png
[options.entry_points]
gui_scripts =
qweechat = "qweechat:iMain"
[easy_install]
zip_ok = false
[flake8]
jobs = 1
max-line-length = 88
ignore =
E111
E114
E128
E225
E261
E302
E305
E402
E501
E502
E541
E701
E702
E704
E722
E741
F508
F541
W503
W601

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -44,18 +44,20 @@ setup(
'Programming Language :: Python',
'Topic :: Communications :: Chat',
],
packages=['qweechat', 'qweechat.weechat'],
packages=['qweechat', 'qweechat.weechat', 'qweechat.data.icons'],
include_package_data=True,
package_data={'qweechat': ['data/icons/*.png']},
entry_points={
'gui_scripts': [
'qweechat = qweechat.qweechat:main',
'qweechat = qweechat.qweechat:iMain',
],
'console_scripts': [
'qweechat-testproto = qweechat.weechat.testproto:main',
]
},
install_requires=[
'PySide6',
'PyQt5',
'qtconsole',
],
zip_safe = False,
)

63
setup.py.dst Normal file
View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# 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 <http://www.gnu.org/licenses/>.
#
from setuptools import setup
from qweechat.version import qweechat_version
DESCRIPTION = 'Qt remote GUI for WeeChat'
setup(
name='qweechat',
version=qweechat_version(),
description=DESCRIPTION,
long_description=DESCRIPTION,
author='Sébastien Helleu',
author_email='flashcode@flashtux.org',
url='https://weechat.org/',
license='GPL3',
keywords='weechat qt gui',
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: X11 Applications :: Qt',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License v3 '
'or later (GPLv3+)',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Communications :: Chat',
],
packages=['qweechat', 'qweechat.weechat', 'qweechat.data.icons'],
include_package_data=True,
package_data={'qweechat': ['data/icons/*.png']},
entry_points={
'gui_scripts': [
'qweechat = qweechat.qweechat:iMain',
],
'console_scripts': [
'qweechat-testproto = qweechat.weechat.testproto:main',
]
},
install_requires=[
'PyQt5',
'qtconsole',
],
zip_safe = False,
)

0
src/__init__.py Normal file
View File

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#

122
src/qweechat/__main__.py Normal file
View File

@ -0,0 +1,122 @@
import os
import sys
import traceback
import logging
from qtpy import QtWidgets, QtGui, QtCore
from qtpy.QtWidgets import QApplication
global LOG
LOG = logging.getLogger('qweechat')
def iMain(lArgs=None):
try:
from qweechat import qweechat
from qweechat.config import write
LOG.info("Loading WeechatConsole")
except ImportError as e:
LOG.error(f"ImportError Loading import qweechat {e} {sys.path}")
LOG.debug(traceback.print_exc())
return 1
from qtpy.QtWidgets import QApplication
_app = QApplication([])
# is this still needed?
if sys.platform == 'Linux' and \
hasattr(QtCore.Qt, 'AA_X11InitThreads'):
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
try:
# WeeChat backported from PySide6 to PyQt5
LOG.info("Adding WeechatConsole")
class WeechatConsole(qweechat.MainWindow):
def __init__(self, *args):
qweechat.MainWindow.__init__(self, *args)
def closeEvent(self, event):
"""Called when QWeeChat window is closed."""
self.network.disconnect_weechat()
if self.network.debug_dialog:
self.network.debug_dialog.close()
write(self.config)
except Exception as e:
LOG.exception(f"ERROR WeechatConsole {e}")
MainWindow = None
return 2
size = 12
font_name = "Courier New"
font_name = "DejaVu Sans Mono"
try:
LOG.info("Creating WeechatConsole")
_we = WeechatConsole()
_we.show()
_we.setWindowTitle('File/Connect to 127.0.0.1:9000')
# Fix the pyconsole geometry
try:
font = _we.buffers[0].widget.chat.defaultFont()
font.setFamily(font_name)
font.setBold(True)
if font_width is None:
font_width = QFontMetrics(font).width('M')
_we.setFont(font)
except Exception as e:
# LOG.debug(e)
font_width = size
geometry = _we.geometry()
# make this configable?
geometry.setWidth(int(font_width*70))
geometry.setHeight(int(font_width*(2+24)*11/8))
_we.setGeometry(geometry)
#? QtCore.QSize()
_we.resize(int(font_width*80+20), int(font_width*(2+24)*11/8))
_we.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Preferred)
_we.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
LOG.info("Showing WeechatConsole")
_we.show()
# or _we.eval_in_thread()
#? return 0
except Exception as e:
LOG.exception(f"Error creating WeechatConsole {e}")
return 4
LOG.info("_execute_app")
# self._app.lastWindowClosed.connect(self._app.quit)
while True:
try:
_app.exec_()
except Exception as ex:
LOG.error('Unhandled exception: ' + str(ex))
return 5
else:
pass # break
if __name__ == '__main__':
iRet = 0
try:
iRet = iMain(sys.argv[1:])
except KeyboardInterrupt:
iRet = 0
except SystemExit as e:
iRet = e
except Exception as e:
import traceback
sys.stderr.write(f"Exception from main {e}" \
+'\n' + traceback.format_exc() +'\n' )
iRet = 1
# Exception ignored in: <module 'threading' from '/usr/lib/python3.9/threading.py'>
# File "/usr/lib/python3.9/threading.py", line 1428, in _shutdown
# lock.acquire()
# gevent.exceptions.LoopExit as e:
# This operation would block forever
# sys.stderr.write('Calling sys.exit' +'\n')
# sys.exit(iRet)

View File

@ -2,7 +2,7 @@
#
# about.py - about dialog box
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -22,7 +22,7 @@
"""About dialog box."""
from PySide6 import QtCore, QtWidgets as QtGui
from qtpy import QtCore, QtWidgets as QtGui
from qweechat.version import qweechat_version
@ -46,7 +46,7 @@ class AboutDialog(QtGui.QDialog):
vbox = QtGui.QVBoxLayout()
messages = [
f'<b>{app_name}</b> {qweechat_version()}',
f'© 2011-2021 {author}',
f'© 2011-2022 {author}',
'',
f'<a href="{weechat_site}">{weechat_site}</a>',
'',

View File

@ -2,7 +2,7 @@
#
# buffer.py - management of WeeChat buffers/nicklist
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -24,13 +24,13 @@
from pkg_resources import resource_filename
from PySide6 import QtCore, QtGui, QtWidgets
from qtpy import QtCore, QtGui, QtWidgets
from qtpy.QtCore import Signal
from qweechat.chat import ChatTextEdit
from qweechat.input import InputLineEdit
from qweechat.weechat import color
class GenericListWidget(QtWidgets.QListWidget):
"""Generic QListWidget with dynamic size."""
@ -145,7 +145,7 @@ class BufferWidget(QtWidgets.QWidget):
class Buffer(QtCore.QObject):
"""A WeeChat buffer."""
bufferInput = QtCore.Signal(str, str)
bufferInput = Signal(str, str)
def __init__(self, data=None):
QtCore.QObject.__init__(self)

View File

@ -2,7 +2,7 @@
#
# chat.py - chat area
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -24,9 +24,9 @@
import datetime
from PySide6 import QtCore, QtWidgets, QtGui
from qtpy import QtCore, QtWidgets, QtGui
from qweechat import config
from qweechat.config import color_options
from qweechat.weechat import color
@ -62,7 +62,7 @@ class ChatTextEdit(QtWidgets.QTextEdit):
'/': True
}
}
self._color = color.Color(config.color_options(), self.debug)
self._color = color.Color(color_options(), self.debug)
def display(self, time, prefix, text, forcecolor=None):
if time == 0:

View File

@ -2,7 +2,7 @@
#
# config.py - configuration for QWeeChat
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -33,14 +33,15 @@ CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR
CONFIG_DEFAULT_RELAY_LINES = 50
CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color')
CONFIG_DEFAULT_OPTIONS = (('relay.server', ''),
('relay.port', ''),
CONFIG_DEFAULT_OPTIONS = (('relay.hostname', '127.0.0.1'),
('relay.port', '9000'),
('relay.ssl', 'off'),
('relay.password', ''),
('relay.autoconnect', 'off'),
('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)),
('look.debug', 'off'),
('look.statusbar', 'off'))
('look.statusbar', 'on'))
CONFIG_DEFAULT_NOTIFICATION_OPTIONS = tuple
# Default colors for WeeChat color options (option name, #rgb value)
CONFIG_DEFAULT_COLOR_OPTIONS = (

View File

@ -2,7 +2,7 @@
#
# connection.py - connection window
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -22,7 +22,7 @@
"""Connection window."""
from PySide6 import QtGui, QtWidgets
from qtpy import QtGui, QtWidgets
class ConnectionDialog(QtWidgets.QDialog):
@ -40,22 +40,30 @@ class ConnectionDialog(QtWidgets.QDialog):
self.fields = {}
focus = None
# server
grid.addWidget(QtWidgets.QLabel('<b>Server</b>'), 0, 0)
# hostname
grid.addWidget(QtWidgets.QLabel('<b>Hostname</b>'), 0, 0)
line_edit = QtWidgets.QLineEdit()
line_edit.setFixedWidth(200)
value = self.values.get('server', '')
value = self.values.get('hostname', '')
if value in ['None', None]:
value = '0'
elif type(value) == int:
value = str(value)
line_edit.insert(value)
grid.addWidget(line_edit, 0, 1)
self.fields['server'] = line_edit
self.fields['hostname'] = line_edit
if not focus and not value:
focus = 'server'
focus = 'hostname'
# port / SSL
grid.addWidget(QtWidgets.QLabel('<b>Port</b>'), 1, 0)
line_edit = QtWidgets.QLineEdit()
line_edit.setFixedWidth(200)
value = self.values.get('port', '')
if value in ['None', None]:
value = '0'
elif type(value) == int:
value = str(value)
line_edit.insert(value)
grid.addWidget(line_edit, 1, 1)
self.fields['port'] = line_edit
@ -73,6 +81,10 @@ class ConnectionDialog(QtWidgets.QDialog):
line_edit.setFixedWidth(200)
line_edit.setEchoMode(QtWidgets.QLineEdit.Password)
value = self.values.get('password', '')
if value in ['None', None]:
value = '0'
elif type(value) == int:
value = str(value)
line_edit.insert(value)
grid.addWidget(line_edit, 2, 1)
self.fields['password'] = line_edit

View File

@ -4,7 +4,7 @@ Copyright and license for images
Files: weechat.png, bullet_green_8x8.png, bullet_yellow_8x8.png
Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
Released under GPLv3.

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 384 B

View File

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 375 B

View File

Before

Width:  |  Height:  |  Size: 813 B

After

Width:  |  Height:  |  Size: 813 B

View File

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

View File

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 713 B

View File

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 596 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -2,7 +2,7 @@
#
# debug.py - debug window
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -22,7 +22,7 @@
"""Debug window."""
from PySide6 import QtWidgets
from qtpy import QtWidgets
from qweechat.chat import ChatTextEdit
from qweechat.input import InputLineEdit
@ -33,7 +33,7 @@ class DebugDialog(QtWidgets.QDialog):
def __init__(self, *args):
QtWidgets.QDialog.__init__(*(self,) + args)
self.resize(1024, 768)
self.resize(800, 600)
self.setWindowTitle('Debug console')
self.chat = ChatTextEdit(debug=True)

View File

@ -2,7 +2,7 @@
#
# input.py - input line for chat and debug window
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -22,15 +22,15 @@
"""Input line for chat and debug window."""
from PySide6 import QtCore, QtWidgets
from qtpy import QtCore, QtWidgets
from qtpy.QtCore import Signal
class InputLineEdit(QtWidgets.QLineEdit):
"""Input line."""
bufferSwitchPrev = QtCore.Signal()
bufferSwitchNext = QtCore.Signal()
textSent = QtCore.Signal(str)
bufferSwitchPrev = Signal()
bufferSwitchNext = Signal()
textSent = Signal(str)
def __init__(self, scroll_widget):
super().__init__()

View File

@ -2,7 +2,7 @@
#
# network.py - I/O with WeeChat/relay
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -26,7 +26,9 @@ import hashlib
import secrets
import struct
from PySide6 import QtCore, QtNetwork
from qtpy import QtCore, QtNetwork
# from PyQt5.QtCore import pyqtSignal as Signal
from qtpy.QtCore import Signal
from qweechat import config
from qweechat.debug import DebugDialog
@ -47,13 +49,13 @@ _HASH_ALGOS = ':'.join(_HASH_ALGOS_LIST)
_PROTO_HANDSHAKE = f'(handshake) handshake password_hash_algo={_HASH_ALGOS}\n'
# initialize with the password (plain text)
_PROTO_INIT_PWD = 'init password=%(password)s%(totp)s\n'
_PROTO_INIT_PWD = 'init password=%(password)s%(totp)s\n' # nosec
# initialize with the hashed password
_PROTO_INIT_HASH = ('init password_hash='
'%(algo)s:%(salt)s%(iter)s:%(hash)s%(totp)s\n')
_PROTO_SYNC = [
_PROTO_SYNC_CMDS = [
# get buffers
'(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,'
'type,nicklist,title,local_variables',
@ -98,8 +100,8 @@ NETWORK_STATUS = {
class Network(QtCore.QObject):
"""I/O with WeeChat/relay."""
statusChanged = QtCore.Signal(str, str)
messageFromWeechat = QtCore.Signal(QtCore.QByteArray)
statusChanged = Signal(str, str)
messageFromWeechat = Signal(QtCore.QByteArray)
def __init__(self, *args):
super().__init__(*args)
@ -115,7 +117,7 @@ class Network(QtCore.QObject):
def _init_connection(self):
self.status = STATUS_DISCONNECTED
self._server = None
self._hostname = None
self._port = None
self._ssl = None
self._password = None
@ -179,7 +181,7 @@ class Network(QtCore.QObject):
def _build_sync_command(self):
"""Build the sync commands to send to WeeChat."""
cmd = '\n'.join(_PROTO_SYNC) + '\n'
cmd = '\n'.join(_PROTO_SYNC_CMDS) + '\n'
return cmd % {'lines': self._lines}
def handshake_timer_expired(self):
@ -230,15 +232,27 @@ class Network(QtCore.QObject):
def is_connected(self):
"""Return True if the socket is connected, False otherwise."""
return self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState
return self.is_state(at='ConnectedState')
def is_state(self, at='ConnectedState'):
"""Return True if the socket is connected, False otherwise."""
if hasattr(QtNetwork.QAbstractSocket, 'ConnectedState'):
if self._socket.state() == getattr(QtNetwork.QAbstractSocket, at):
return True
return False
if hasattr(QtNetwork.QAbstractSocket, 'SocketState'):
if self._socket.state() == getattr(QtNetwork.QAbstractSocket.SocketState, at):
return True
return False
return False
def is_ssl(self):
"""Return True if SSL is used, False otherwise."""
return self._ssl
def connect_weechat(self, server, port, ssl, password, totp, lines):
def connect_weechat(self, hostname, port, ssl, password, totp, lines):
"""Connect to WeeChat."""
self._server = server
self._hostname = hostname
try:
self._port = int(port)
except ValueError:
@ -250,23 +264,24 @@ class Network(QtCore.QObject):
self._lines = int(lines)
except ValueError:
self._lines = config.CONFIG_DEFAULT_RELAY_LINES
if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
# AttributeError: type object 'QAbstractSocket' has no attribute 'ConnectedState'
if self.is_connected():
return
if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState:
if not self.is_state('UnconnectedState'):
self._socket.abort()
if self._ssl:
self._socket.ignoreSslErrors()
self._socket.connectToHostEncrypted(self._server, self._port)
self._socket.connectToHostEncrypted(self._hostname, self._port)
else:
self._socket.connectToHost(self._server, self._port)
self._socket.connectToHost(self._hostname, self._port)
self.set_status(STATUS_CONNECTING)
def disconnect_weechat(self):
"""Disconnect from WeeChat."""
if self._socket.state() == QtNetwork.QAbstractSocket.UnconnectedState:
if self.is_state('UnconnectedState'):
self.set_status(STATUS_DISCONNECTED)
return
if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
if self.is_state('ConnectedState'):
self.send_to_weechat('quit\n')
self._socket.waitForBytesWritten(1000)
else:
@ -316,7 +331,7 @@ class Network(QtCore.QObject):
def get_options(self):
"""Get connection options."""
return {
'server': self._server,
'hostname': self._hostname,
'port': self._port,
'ssl': 'on' if self._ssl else 'off',
'password': self._password,

509
src/qweechat/preferences.py Normal file
View File

@ -0,0 +1,509 @@
# -*- coding: utf-8 -*-
#
# preferences.py - preferences dialog box
#
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# 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 <http://www.gnu.org/licenses/>.
#
"""Preferences dialog box."""
from qtpy import QtCore, QtWidgets, QtGui
qicon_from_theme = QtGui.QIcon.fromTheme
class PreferencesDialog(QtWidgets.QDialog):
"""Preferences dialog."""
def __init__(self, *args):
QtWidgets.QDialog.__init__(*(self,) + args)
self.setModal(True)
self.setWindowTitle('Preferences')
close_button = QtWidgets.QPushButton('Close')
close_button.pressed.connect(self.close)
hbox = QtWidgets.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(close_button)
hbox.addStretch(1)
vbox = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel('Not yet implemented!')
label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label)
label = QtWidgets.QLabel('')
label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label)
vbox.addLayout(hbox)
self.setLayout(vbox)
self.show()
# prepare for https://github.com/weechat/qweechat/pull/8
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, QtWidgets.QComboBox):
text = field.itemText(field.currentIndex())
data = field.itemData(field.currentIndex())
text = data if data else text
elif isinstance(field, QtWidgets.QCheckBox):
text = "on" if field.isChecked() else "off"
else:
text = field.text()
self.config.set(widget.section_name, key, str(text))
write(self.config)
self.parent.apply_preferences()
self.close()
class PreferencesNotificationBlock(QtWidgets.QVBoxLayout):
"""Display notification settings with drill down to configure."""
def __init__(self, pane, *args):
QtWidgets.QVBoxLayout.__init__(*(self,) + args)
self.section = "notifications"
self.config = QtWidgets.QApplication.instance().config
self.pane = pane
self.stack = QtWidgets.QStackedWidget()
self.table = QtWidgets.QTableWidget()
fg_color = self.table.palette().text().color().name()
self.action_labels = {
"sound": "Play a sound",
"message": "Show a message in a popup",
"file": "Log to a file",
"taskbar": "Mark taskbar entry",
"tray": "Mark systray/indicator",
"command": "Run a command"}
self.action_icons = {
"sound": qicon_from_theme("media-playback-start"),
"message": qicon_from_theme("dialog-information"),
"file": qicon_from_theme("document-export"),
"taskbar": qicon_from_theme("weechat"),
"tray": utils.qicon_tint("ic_hot", fg_color),
"command": qicon_from_theme("system-run")}
self.icon_widget_qss = "padding:0;min-height:10px;min-width:16px;"
self.table.resizeColumnsToContents()
self.table.setColumnCount(2)
self.table.resizeRowsToContents()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.table.setHorizontalHeaderLabels(["State", "Type"])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setHighlightSections(False)
self.table.verticalHeader().setVisible(False)
self.table.setShowGrid(False)
self.table.itemSelectionChanged.connect(self._table_row_changed)
self.buftypes = {}
for key, value in CONFIG_DEFAULT_NOTIFICATION_OPTIONS:
buftype, optkey = key.split(".")
if buftype not in self.buftypes:
self.buftypes[buftype] = {}
self.buftypes[buftype][optkey] = self.config.get(self.section, key)
for buftype, optkey in self.buftypes.items():
self._insert_type(buftype)
self.update_icons()
self.resize_table()
self.addWidget(self.table)
self.addWidget(self.stack)
self.table.selectRow(0)
def _insert_type(self, buftype):
row = self.table.rowCount()
self.table.insertRow(row)
buftype_item = QtWidgets.QTableWidgetItem(buftype)
buftype_item.setTextAlignment(QtCore.Qt.AlignCenter)
self.table.setItem(row, 0, QtWidgets.QTableWidgetItem())
self.table.setItem(row, 1, buftype_item)
subgrid = QtWidgets.QGridLayout()
subgrid.setColumnStretch(2, 1)
subgrid.setSpacing(10)
for key, qicon in self.action_icons.items():
value = self.buftypes[buftype][key]
line = subgrid.rowCount()
label = IconTextLabel(self.action_labels[key], qicon, 16)
checkbox = QtWidgets.QCheckBox()
span = 1
edit = None
if key in ("message", "taskbar", "tray"):
checkbox.setChecked(value == "on")
span = 2
elif key == "sound":
edit = PreferencesFileEdit(
checkbox=checkbox, caption='Select a sound file',
filter='Audio Files (*.wav *.mp3 *.ogg)')
elif key == "file":
edit = PreferencesFileEdit(checkbox=checkbox, mode="save")
else:
edit = PreferencesFileEdit(checkbox=checkbox)
if edit:
edit.insert(value)
subgrid.addWidget(edit, line, 2)
else:
edit = checkbox
subgrid.addWidget(label, line, 1, 1, span)
subgrid.addWidget(checkbox, line, 0)
self.pane.fields[buftype + "." + key] = edit
subpane = QtWidgets.QWidget()
subpane.setLayout(subgrid)
subpane.setMaximumHeight(subgrid.totalMinimumSize().height())
self.stack.addWidget(subpane)
def resize_table(self):
"""Fit the table height to contents."""
height = self.table.horizontalHeader().height()
height = height * (self.table.rowCount() + 1)
height += self.table.contentsMargins().top()
height += self.table.contentsMargins().bottom()
self.table.setMaximumHeight(height)
self.table.setMinimumHeight(height)
self.table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
def update_icons(self):
"""Draw the correct icons in the left col."""
for i in range(self.table.rowCount()):
hbox = QtWidgets.QHBoxLayout()
iconset = QtWidgets.QWidget()
buftype = self.table.item(i, 1).text()
for key, qicon in self.action_icons.items():
field = self.pane.fields[buftype + "." + key]
if isinstance(field, QtWidgets.QCheckBox):
val = "on" if field.isChecked() else "off"
else:
val = field.text()
iconbtn = QtWidgets.QPushButton()
iconbtn.setContentsMargins(0, 0, 0, 0)
iconbtn.setFlat(True)
iconbtn.setFocusPolicy(QtCore.Qt.NoFocus)
if val and val != "off":
iconbtn.setIcon(qicon)
iconbtn.setStyleSheet(self.icon_widget_qss)
iconbtn.setToolTip(key)
iconbtn.clicked.connect(lambda i=i: self.table.selectRow(i))
hbox.addWidget(iconbtn)
iconset.setLayout(hbox)
self.table.setCellWidget(i, 0, iconset)
def _table_row_changed(self):
row = self.table.selectionModel().selectedRows()[0].row()
self.stack.setCurrentIndex(row)
class PreferencesTreeWidget(QtWidgets.QTreeWidget):
"""Widget with tree list of preferences."""
def __init__(self, header_label, *args):
QtWidgets.QTreeWidget.__init__(*(self,) + args)
self.setHeaderLabel(header_label)
self.setRootIsDecorated(False)
self.setMaximumWidth(180)
self.setTextElideMode(QtCore.Qt.ElideNone)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFocusPolicy(QtCore.Qt.NoFocus)
class PreferencesSliderEdit(QtWidgets.QSlider):
"""Percentage slider."""
def __init__(self, *args):
QtWidgets.QSlider.__init__(*(self,) + args)
self.setMinimum(0)
self.setMaximum(100)
self.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.setTickInterval(5)
def insert(self, percent):
self.setValue(int(percent[:-1]))
def text(self):
return str(self.value()) + "%"
class PreferencesColorEdit(QtWidgets.QPushButton):
"""Simple color square that changes based on the color selected."""
def __init__(self, *args):
QtWidgets.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 = QtWidgets.QColorDialog.getColor(self.color)
self.insert(color.name())
def _color_star(self):
self.star = not self.star
self.insert(self.text())
class PreferencesFontEdit(QtWidgets.QWidget):
"""Font entry and selection."""
def __init__(self, *args):
QtWidgets.QWidget.__init__(*(self,) + args)
layout = QtWidgets.QHBoxLayout()
self.checkbox = QtWidgets.QCheckBox()
self.edit = QtWidgets.QLineEdit()
self.font = ""
self.qfont = None
self.button = QtWidgets.QPushButton("C&hoose")
self.button.clicked.connect(self._font_picker)
self.checkbox.toggled.connect(
lambda: self._checkbox_toggled(self.checkbox))
layout.addWidget(self.checkbox)
layout.addWidget(self.edit)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def insert(self, font_str):
"""Insert the font described by the string."""
self.font = font_str
self.edit.insert(font_str)
if font_str:
self.qfont = utils.Font.str_to_qfont(font_str)
self.edit.setFont(self.qfont)
self.checkbox.setChecked(True)
self._checkbox_toggled(self.checkbox)
else:
self.checkbox.setChecked(False)
self.qfont = None
self._checkbox_toggled(self.checkbox)
def text(self):
"""Returns the human readable font string."""
return self.font
def _font_picker(self):
font, ok = QtWidgets.QFontDialog.getFont(self.qfont)
if ok:
self.insert(utils.Font.qfont_to_str(font))
def _checkbox_toggled(self, button):
if button.isChecked() is False and not self.font == "":
self.insert("")
self.edit.setEnabled(button.isChecked())
self.button.setEnabled(button.isChecked())
class PreferencesFileEdit(QtWidgets.QWidget):
"""File entry and selection."""
def __init__(self, checkbox=None, caption="Select a file", filter=None,
mode="open", *args):
QtWidgets.QWidget.__init__(*(self,) + args)
layout = QtWidgets.QHBoxLayout()
self.caption = caption
self.filter = filter
self.edit = QtWidgets.QLineEdit()
self.file_str = ""
self.mode = mode
self.button = QtWidgets.QPushButton("B&rowse")
self.button.clicked.connect(self._file_picker)
if checkbox:
self.checkbox = checkbox
else:
self.checkbox = QtWidgets.QCheckBox()
layout.addWidget(self.checkbox)
self.checkbox.toggled.connect(
lambda: self._checkbox_toggled(self.checkbox))
layout.addWidget(self.edit)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def insert(self, file_str):
"""Insert the file."""
self.file_str = file_str
self.edit.insert(file_str)
if file_str:
self.checkbox.setChecked(True)
self._checkbox_toggled(self.checkbox)
else:
self.checkbox.setChecked(False)
self._checkbox_toggled(self.checkbox)
def text(self):
"""Returns the human readable font string."""
return self.file_str
def _file_picker(self):
path = ""
if self.mode == "save":
fn = QtWidgets.QFileDialog.getSaveFileName
else:
fn = QtWidgets.QFileDialog.getOpenFileName
filename, fil = fn(self, self.caption, path, self.filter, self.filter)
if filename:
self.insert(filename)
def _checkbox_toggled(self, button):
if button.isChecked() is False and not self.file_str == "":
self.insert("")
self.edit.setEnabled(button.isChecked())
self.button.setEnabled(button.isChecked())
class PreferencesPaneWidget(QtWidgets.QWidget):
"""
Widget with (from top to bottom):
title, chat + nicklist (optional) + prompt/input.
"""
disabled_fields = ["show_hostnames", "hide_nick_changes",
"hide_join_and_part"]
def __init__(self, section, section_name):
QtWidgets.QWidget.__init__(self)
self.grid = QtWidgets.QGridLayout()
self.grid.setAlignment(QtCore.Qt.AlignTop)
self.section = section
self.section_name = section_name
self.fields = {}
self.setLayout(self.grid)
self.grid.setColumnStretch(2, 1)
self.grid.setSpacing(10)
self.int_validator = QtWidgets.QIntValidator(0, 2147483647, self)
toolbar_icons = [
('ToolButtonFollowStyle', 'Default'),
('ToolButtonIconOnly', 'Icon Only'),
('ToolButtonTextOnly', 'Text Only'),
('ToolButtonTextBesideIcon', 'Text Alongside Icons'),
('ToolButtonTextUnderIcon', 'Text Under Icons')]
tray_options = [
('always', 'Always'),
('unread', 'On Unread Messages'),
('never', 'Never'),
]
list_positions = [
('left', 'Left'),
('right', 'Right'),
]
sort_options = ['A-Z Ranked', 'A-Z', 'Z-A Ranked', 'Z-A']
focus_opts = ["requested", "always", "never"]
self.comboboxes = {"style": QtWidgets.QStyleFactory.keys(),
"position": list_positions,
"toolbar_icons": toolbar_icons,
"focus_new_tabs": focus_opts,
"tray_icon": tray_options,
"sort": sort_options}
def addItem(self, key, value, default):
"""Add a key-value pair."""
line = len(self.fields)
name = key.split(".")[-1:][0].capitalize().replace("_", " ")
label = QtWidgets.QLabel(name)
start = 0
if self.section == "color":
start = 2 * (line % 2)
line = line // 2
edit = PreferencesColorEdit()
edit.setFixedWidth(edit.sizeHint().height())
edit.insert(value)
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
elif key == "custom_stylesheet":
edit = PreferencesFileEdit(caption='Select QStyleSheet File',
filter='*.qss')
edit.insert(value)
elif name.lower()[-5:] == "sound":
edit = PreferencesFileEdit(
caption='Select a sound file',
filter='Audio Files (*.wav *.mp3 *.ogg)')
edit.insert(value)
elif name.lower()[-4:] == "font":
edit = PreferencesFontEdit()
edit.setFixedWidth(200)
edit.insert(value)
elif key in self.comboboxes.keys():
edit = QtWidgets.QComboBox()
if len(self.comboboxes[key][0]) == 2:
for keyvalue in self.comboboxes[key]:
edit.addItem(keyvalue[1], keyvalue[0])
# if self.section == "nicks" and key == "position":
# edit.addItem("below", "Below Buffer List")
# edit.addItem("above", "Above Buffer List")
edit.setCurrentIndex(edit.findData(value))
else:
edit.addItems(self.comboboxes[key])
edit.setCurrentIndex(edit.findText(value))
edit.setFixedWidth(200)
elif default in ["on", "off"]:
edit = QtWidgets.QCheckBox()
edit.setChecked(value == "on")
elif default[-1:] == "%":
edit = PreferencesSliderEdit(QtCore.Qt.Horizontal)
edit.setFixedWidth(200)
edit.insert(value)
else:
edit = QtWidgets.QLineEdit()
edit.setFixedWidth(200)
edit.insert(value)
if default.isdigit() or key == "port":
edit.setValidator(self.int_validator)
if key == 'password':
edit.setEchoMode(QtWidgets.QLineEdit.Password)
if key in self.disabled_fields:
edit.setDisabled(True)
self.grid.addWidget(label, line, start + 0)
self.grid.addWidget(edit, line, start + 1)
self.fields[key] = edit
class IconTextLabel(QtWidgets.QWidget):
"""An icon next to text."""
def __init__(self, text=None, icon=None, extent=None):
QtWidgets.QWidget.__init__(self)
text_label = QtWidgets.QLabel(text)
if not extent:
extent = text_label.height()
icon_label = QtWidgets.QLabel()
pixmap = icon.pixmap(extent, QtWidgets.QIcon.Normal, QtWidgets.QIcon.On)
icon_label.setPixmap(pixmap)
label_layout = QtWidgets.QHBoxLayout()
label_layout.addWidget(icon_label)
label_layout.addWidget(text_label)
label_layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.setLayout(label_layout)

View File

@ -0,0 +1,555 @@
# -*- coding: utf-8 -*-
#
# preferences.py - preferences dialog box
#
# Copyright (C) 2016 Ricky Brent <ricky@rickybrent.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
from qweechat.config import (CONFIG_DEFAULT_SECTIONS,
CONFIG_DEFAULT_NOTIFICATION_OPTIONS,
CONFIG_DEFAULT_OPTIONS, write)
import utils
from qtpy import QtCore, QtGui
qicon_from_theme = QtGui.QIcon.fromTheme
class PreferencesDialog(QtGui.QDialog):
"""Preferences dialog."""
custom_sections = {
"look": "Look",
"input": "Input Box",
"nicks": "Nick List",
"buffers": "Buffer List",
"buffer_flags": False,
"notifications": "Notifications",
"color": "Colors",
"relay": "Relay/Connection"
}
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 CONFIG_DEFAULT_SECTIONS:
item = QtGui.QTreeWidgetItem(section)
name = section
item.setText(0, section.title())
if section in self.custom_sections:
if not self.custom_sections[section]:
continue
item.setText(0, self.custom_sections[section])
section_panes[section] = PreferencesPaneWidget(section, name)
self.list_panes.addTopLevelItem(item)
self.stacked_panes.addWidget(section_panes[section])
for setting, default in CONFIG_DEFAULT_OPTIONS:
section_key = setting.split(".")
section = section_key[0]
key = ".".join(section_key[1:])
section_panes[section].addItem(
key, self.config.get(section, key), default)
for key, value in self.config.items("color"):
section_panes["color"].addItem(key, value, False)
notification_field_count = len(section_panes["notifications"].fields)
notification = PreferencesNotificationBlock(
section_panes["notifications"])
section_panes["notifications"].grid.addLayout(
notification, notification_field_count, 0, 1, -1)
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())
data = field.itemData(field.currentIndex())
text = data if data else text
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))
write(self.config)
self.parent.apply_preferences()
self.close()
class PreferencesNotificationBlock(QtGui.QVBoxLayout):
"""Display notification settings with drill down to configure."""
def __init__(self, pane, *args):
QtGui.QVBoxLayout.__init__(*(self,) + args)
self.section = "notifications"
self.config = QtGui.QApplication.instance().config
self.pane = pane
self.stack = QtGui.QStackedWidget()
self.table = QtGui.QTableWidget()
fg_color = self.table.palette().text().color().name()
self.action_labels = {
"sound": "Play a sound",
"message": "Show a message in a popup",
"file": "Log to a file",
"taskbar": "Mark taskbar entry",
"tray": "Mark systray/indicator",
"command": "Run a command"}
self.action_icons = {
"sound": qicon_from_theme("media-playback-start"),
"message": qicon_from_theme("dialog-information"),
"file": qicon_from_theme("document-export"),
"taskbar": qicon_from_theme("weechat"),
"tray": utils.qicon_tint("ic_hot", fg_color),
"command": qicon_from_theme("system-run")}
self.icon_widget_qss = "padding:0;min-height:10px;min-width:16px;"
self.table.resizeColumnsToContents()
self.table.setColumnCount(2)
self.table.resizeRowsToContents()
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
self.table.setHorizontalHeaderLabels(["State", "Type"])
self.table.horizontalHeader().setStretchLastSection(True)
self.table.horizontalHeader().setHighlightSections(False)
self.table.verticalHeader().setVisible(False)
self.table.setShowGrid(False)
self.table.itemSelectionChanged.connect(self._table_row_changed)
self.buftypes = {}
for key, value in CONFIG_DEFAULT_NOTIFICATION_OPTIONS:
buftype, optkey = key.split(".")
if buftype not in self.buftypes:
self.buftypes[buftype] = {}
self.buftypes[buftype][optkey] = self.config.get(self.section, key)
for buftype, optkey in self.buftypes.items():
self._insert_type(buftype)
self.update_icons()
self.resize_table()
self.addWidget(self.table)
self.addWidget(self.stack)
self.table.selectRow(0)
def _insert_type(self, buftype):
row = self.table.rowCount()
self.table.insertRow(row)
buftype_item = QtGui.QTableWidgetItem(buftype)
buftype_item.setTextAlignment(QtCore.Qt.AlignCenter)
self.table.setItem(row, 0, QtGui.QTableWidgetItem())
self.table.setItem(row, 1, buftype_item)
subgrid = QtGui.QGridLayout()
subgrid.setColumnStretch(2, 1)
subgrid.setSpacing(10)
for key, qicon in self.action_icons.items():
value = self.buftypes[buftype][key]
line = subgrid.rowCount()
label = IconTextLabel(self.action_labels[key], qicon, 16)
checkbox = QtGui.QCheckBox()
span = 1
edit = None
if key in ("message", "taskbar", "tray"):
checkbox.setChecked(value == "on")
span = 2
elif key == "sound":
edit = PreferencesFileEdit(
checkbox=checkbox, caption='Select a sound file',
filter='Audio Files (*.wav *.mp3 *.ogg)')
elif key == "file":
edit = PreferencesFileEdit(checkbox=checkbox, mode="save")
else:
edit = PreferencesFileEdit(checkbox=checkbox)
if edit:
edit.insert(value)
subgrid.addWidget(edit, line, 2)
else:
edit = checkbox
subgrid.addWidget(label, line, 1, 1, span)
subgrid.addWidget(checkbox, line, 0)
self.pane.fields[buftype + "." + key] = edit
subpane = QtGui.QWidget()
subpane.setLayout(subgrid)
subpane.setMaximumHeight(subgrid.totalMinimumSize().height())
self.stack.addWidget(subpane)
def resize_table(self):
"""Fit the table height to contents."""
height = self.table.horizontalHeader().height()
height = height * (self.table.rowCount() + 1)
height += self.table.contentsMargins().top()
height += self.table.contentsMargins().bottom()
self.table.setMaximumHeight(height)
self.table.setMinimumHeight(height)
self.table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
def update_icons(self):
"""Draw the correct icons in the left col."""
for i in range(self.table.rowCount()):
hbox = QtGui.QHBoxLayout()
iconset = QtGui.QWidget()
buftype = self.table.item(i, 1).text()
for key, qicon in self.action_icons.items():
field = self.pane.fields[buftype + "." + key]
if isinstance(field, QtGui.QCheckBox):
val = "on" if field.isChecked() else "off"
else:
val = field.text()
iconbtn = QtGui.QPushButton()
iconbtn.setContentsMargins(0, 0, 0, 0)
iconbtn.setFlat(True)
iconbtn.setFocusPolicy(QtCore.Qt.NoFocus)
if val and val != "off":
iconbtn.setIcon(qicon)
iconbtn.setStyleSheet(self.icon_widget_qss)
iconbtn.setToolTip(key)
iconbtn.clicked.connect(lambda i=i: self.table.selectRow(i))
hbox.addWidget(iconbtn)
iconset.setLayout(hbox)
self.table.setCellWidget(i, 0, iconset)
def _table_row_changed(self):
row = self.table.selectionModel().selectedRows()[0].row()
self.stack.setCurrentIndex(row)
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(180)
self.setTextElideMode(QtCore.Qt.ElideNone)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFocusPolicy(QtCore.Qt.NoFocus)
class PreferencesSliderEdit(QtGui.QSlider):
"""Percentage slider."""
def __init__(self, *args):
QtGui.QSlider.__init__(*(self,) + args)
self.setMinimum(0)
self.setMaximum(100)
self.setTickPosition(QtGui.QSlider.TicksBelow)
self.setTickInterval(5)
def insert(self, percent):
self.setValue(int(percent[:-1]))
def text(self):
return str(self.value()) + "%"
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.color)
self.insert(color.name())
def _color_star(self):
self.star = not self.star
self.insert(self.text())
class PreferencesFontEdit(QtGui.QWidget):
"""Font entry and selection."""
def __init__(self, *args):
QtGui.QWidget.__init__(*(self,) + args)
layout = QtGui.QHBoxLayout()
self.checkbox = QtGui.QCheckBox()
self.edit = QtGui.QLineEdit()
self.font = ""
self.qfont = None
self.button = QtGui.QPushButton("C&hoose")
self.button.clicked.connect(self._font_picker)
self.checkbox.toggled.connect(
lambda: self._checkbox_toggled(self.checkbox))
layout.addWidget(self.checkbox)
layout.addWidget(self.edit)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def insert(self, font_str):
"""Insert the font described by the string."""
self.font = font_str
self.edit.insert(font_str)
if font_str:
self.qfont = utils.Font.str_to_qfont(font_str)
self.edit.setFont(self.qfont)
self.checkbox.setChecked(True)
self._checkbox_toggled(self.checkbox)
else:
self.checkbox.setChecked(False)
self.qfont = None
self._checkbox_toggled(self.checkbox)
def text(self):
"""Returns the human readable font string."""
return self.font
def _font_picker(self):
font, ok = QtGui.QFontDialog.getFont(self.qfont)
if ok:
self.insert(utils.Font.qfont_to_str(font))
def _checkbox_toggled(self, button):
if button.isChecked() is False and not self.font == "":
self.insert("")
self.edit.setEnabled(button.isChecked())
self.button.setEnabled(button.isChecked())
class PreferencesFileEdit(QtGui.QWidget):
"""File entry and selection."""
def __init__(self, checkbox=None, caption="Select a file", filter=None,
mode="open", *args):
QtGui.QWidget.__init__(*(self,) + args)
layout = QtGui.QHBoxLayout()
self.caption = caption
self.filter = filter
self.edit = QtGui.QLineEdit()
self.file_str = ""
self.mode = mode
self.button = QtGui.QPushButton("B&rowse")
self.button.clicked.connect(self._file_picker)
if checkbox:
self.checkbox = checkbox
else:
self.checkbox = QtGui.QCheckBox()
layout.addWidget(self.checkbox)
self.checkbox.toggled.connect(
lambda: self._checkbox_toggled(self.checkbox))
layout.addWidget(self.edit)
layout.addWidget(self.button)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def insert(self, file_str):
"""Insert the file."""
self.file_str = file_str
self.edit.insert(file_str)
if file_str:
self.checkbox.setChecked(True)
self._checkbox_toggled(self.checkbox)
else:
self.checkbox.setChecked(False)
self._checkbox_toggled(self.checkbox)
def text(self):
"""Returns the human readable font string."""
return self.file_str
def _file_picker(self):
path = ""
if self.mode == "save":
fn = QtGui.QFileDialog.getSaveFileName
else:
fn = QtGui.QFileDialog.getOpenFileName
filename, fil = fn(self, self.caption, path, self.filter, self.filter)
if filename:
self.insert(filename)
def _checkbox_toggled(self, button):
if button.isChecked() is False and not self.file_str == "":
self.insert("")
self.edit.setEnabled(button.isChecked())
self.button.setEnabled(button.isChecked())
class PreferencesPaneWidget(QtGui.QWidget):
"""
Widget with (from top to bottom):
title, chat + nicklist (optional) + prompt/input.
"""
disabled_fields = ["show_hostnames", "hide_nick_changes",
"hide_join_and_part"]
def __init__(self, section, section_name):
QtGui.QWidget.__init__(self)
self.grid = QtGui.QGridLayout()
self.grid.setAlignment(QtCore.Qt.AlignTop)
self.section = section
self.section_name = section_name
self.fields = {}
self.setLayout(self.grid)
self.grid.setColumnStretch(2, 1)
self.grid.setSpacing(10)
self.int_validator = QtGui.QIntValidator(0, 2147483647, self)
toolbar_icons = [
('ToolButtonFollowStyle', 'Default'),
('ToolButtonIconOnly', 'Icon Only'),
('ToolButtonTextOnly', 'Text Only'),
('ToolButtonTextBesideIcon', 'Text Alongside Icons'),
('ToolButtonTextUnderIcon', 'Text Under Icons')]
tray_options = [
('always', 'Always'),
('unread', 'On Unread Messages'),
('never', 'Never'),
]
list_positions = [
('left', 'Left'),
('right', 'Right'),
]
sort_options = ['A-Z Ranked', 'A-Z', 'Z-A Ranked', 'Z-A']
focus_opts = ["requested", "always", "never"]
self.comboboxes = {"style": QtGui.QStyleFactory.keys(),
"position": list_positions,
"toolbar_icons": toolbar_icons,
"focus_new_tabs": focus_opts,
"tray_icon": tray_options,
"sort": sort_options}
def addItem(self, key, value, default):
"""Add a key-value pair."""
line = len(self.fields)
name = key.split(".")[-1:][0].capitalize().replace("_", " ")
label = QtGui.QLabel(name)
start = 0
if self.section == "color":
start = 2 * (line % 2)
line = line // 2
edit = PreferencesColorEdit()
edit.setFixedWidth(edit.sizeHint().height())
edit.insert(value)
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
elif key == "custom_stylesheet":
edit = PreferencesFileEdit(caption='Select QStyleSheet File',
filter='*.qss')
edit.insert(value)
elif name.lower()[-5:] == "sound":
edit = PreferencesFileEdit(
caption='Select a sound file',
filter='Audio Files (*.wav *.mp3 *.ogg)')
edit.insert(value)
elif name.lower()[-4:] == "font":
edit = PreferencesFontEdit()
edit.setFixedWidth(200)
edit.insert(value)
elif key in self.comboboxes.keys():
edit = QtGui.QComboBox()
if len(self.comboboxes[key][0]) == 2:
for keyvalue in self.comboboxes[key]:
edit.addItem(keyvalue[1], keyvalue[0])
# if self.section == "nicks" and key == "position":
# edit.addItem("below", "Below Buffer List")
# edit.addItem("above", "Above Buffer List")
edit.setCurrentIndex(edit.findData(value))
else:
edit.addItems(self.comboboxes[key])
edit.setCurrentIndex(edit.findText(value))
edit.setFixedWidth(200)
elif default in ["on", "off"]:
edit = QtGui.QCheckBox()
edit.setChecked(value == "on")
elif default[-1:] == "%":
edit = PreferencesSliderEdit(QtCore.Qt.Horizontal)
edit.setFixedWidth(200)
edit.insert(value)
else:
edit = QtGui.QLineEdit()
edit.setFixedWidth(200)
edit.insert(value)
if default.isdigit() or key == "port":
edit.setValidator(self.int_validator)
if key == 'password':
edit.setEchoMode(QtGui.QLineEdit.Password)
if key in self.disabled_fields:
edit.setDisabled(True)
self.grid.addWidget(label, line, start + 0)
self.grid.addWidget(edit, line, start + 1)
self.fields[key] = edit
class IconTextLabel(QtGui.QWidget):
"""An icon next to text."""
def __init__(self, text=None, icon=None, extent=None):
QtGui.QWidget.__init__(self)
text_label = QtGui.QLabel(text)
if not extent:
extent = text_label.height()
icon_label = QtGui.QLabel()
pixmap = icon.pixmap(extent, QtGui.QIcon.Normal, QtGui.QIcon.On)
icon_label.setPixmap(pixmap)
label_layout = QtGui.QHBoxLayout()
label_layout.addWidget(icon_label)
label_layout.addWidget(text_label)
label_layout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.setLayout(label_layout)

View File

@ -2,7 +2,7 @@
#
# qweechat.py - WeeChat remote GUI using Qt toolkit
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -35,15 +35,16 @@ It requires requires WeeChat 0.3.7 or newer, running on local or remote host.
import sys
import traceback
import signal
from pkg_resources import resource_filename
from PySide6 import QtCore, QtGui, QtWidgets
from qtpy import QtCore, QtGui, QtWidgets
from qweechat import config
from qweechat.config import read, write
from qweechat.about import AboutDialog
from qweechat.buffer import BufferListWidget, Buffer
from qweechat.connection import ConnectionDialog
from qweechat.network import Network, STATUS_DISCONNECTED, NETWORK_STATUS
from qweechat.network import Network, STATUS_DISCONNECTED
from qweechat.preferences import PreferencesDialog
from qweechat.weechat import protocol
@ -52,16 +53,16 @@ APP_NAME = 'QWeeChat'
AUTHOR = 'Sébastien Helleu'
WEECHAT_SITE = 'https://weechat.org/'
# not QFrame
class MainWindow(QtWidgets.QMainWindow):
"""Main window."""
def __init__(self, *args):
super().__init__(*args)
self.config = config.read()
self.config = read()
self.resize(1000, 600)
self.resize(800, 600)
self.setWindowTitle(APP_NAME)
self.about_dialog = None
@ -87,10 +88,16 @@ class MainWindow(QtWidgets.QMainWindow):
splitter.addWidget(self.list_buffers)
splitter.addWidget(self.stacked_buffers)
self.list_buffers.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Preferred)
self.stacked_buffers.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
# MainWindow
self.setCentralWidget(splitter)
if self.config.getboolean('look', 'statusbar'):
self.statusBar().visible = True
self.statusBar().visible = True
# actions for menu and toolbar
actions_def = {
@ -139,7 +146,7 @@ class MainWindow(QtWidgets.QMainWindow):
}
self.actions = {}
for name, action in list(actions_def.items()):
self.actions[name] = QtGui.QAction(
self.actions[name] = QtWidgets.QAction(
QtGui.QIcon(
resource_filename(__name__, 'data/icons/%s' % action[0])),
name.capitalize(), self)
@ -164,20 +171,22 @@ class MainWindow(QtWidgets.QMainWindow):
self.network_status.setFixedWidth(200)
self.network_status.setContentsMargins(0, 0, 10, 0)
self.network_status.setAlignment(QtCore.Qt.AlignRight)
if hasattr(self.menu, 'setCornerWidget'):
self.menu.setCornerWidget(self.network_status,
QtCore.Qt.TopRightCorner)
if hasattr(self, 'menuBar'):
if hasattr(self.menu, 'setCornerWidget'):
self.menu.setCornerWidget(self.network_status,
QtCore.Qt.TopRightCorner)
self.network_status_set(STATUS_DISCONNECTED)
# toolbar
toolbar = self.addToolBar('toolBar')
toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
toolbar.addActions([self.actions['connect'],
self.actions['disconnect'],
self.actions['debug'],
self.actions['preferences'],
self.actions['about'],
self.actions['quit']])
if hasattr(self, 'addToolBar'):
toolbar = self.addToolBar('toolBar')
toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
toolbar.addActions([self.actions['connect'],
self.actions['disconnect'],
self.actions['debug'],
self.actions['preferences'],
self.actions['about'],
self.actions['quit']])
self.buffers[0].widget.input.setFocus()
@ -188,12 +197,12 @@ class MainWindow(QtWidgets.QMainWindow):
# auto-connect to relay
if self.config.getboolean('relay', 'autoconnect'):
self.network.connect_weechat(
server=self.config.get('relay', 'server'),
port=self.config.get('relay', 'port'),
ssl=self.config.getboolean('relay', 'ssl'),
password=self.config.get('relay', 'password'),
hostname=self.config.get('relay', 'hostname', fallback='127.0.0.1'),
port=self.config.get('relay', 'port', fallback='9000'),
ssl=self.config.getboolean('relay', 'ssl', fallback=''),
password=self.config.get('relay', 'password', fallback=''),
totp=None,
lines=self.config.get('relay', 'lines'),
lines=self.config.get('relay', 'lines', fallback=''),
)
self.show()
@ -230,8 +239,8 @@ class MainWindow(QtWidgets.QMainWindow):
def open_connection_dialog(self):
"""Open a dialog with connection settings."""
values = {}
for option in ('server', 'port', 'ssl', 'password', 'lines'):
values[option] = self.config.get('relay', option)
for option in ('hostname', 'port', 'ssl', 'password', 'lines'):
values[option] = self.config.get('relay', option, fallback='')
self.connection_dialog = ConnectionDialog(values, self)
self.connection_dialog.dialog_buttons.accepted.connect(
self.connect_weechat)
@ -239,7 +248,7 @@ class MainWindow(QtWidgets.QMainWindow):
def connect_weechat(self):
"""Connect to WeeChat."""
self.network.connect_weechat(
server=self.connection_dialog.fields['server'].text(),
hostname=self.connection_dialog.fields['hostname'].text(),
port=self.connection_dialog.fields['port'].text(),
ssl=self.connection_dialog.fields['ssl'].isChecked(),
password=self.connection_dialog.fields['password'].text(),
@ -258,8 +267,12 @@ class MainWindow(QtWidgets.QMainWindow):
def network_status_set(self, status):
"""Set the network status."""
pal = self.network_status.palette()
pal.setColor(self.network_status.foregroundRole(),
try:
pal.setColor(self.network_status.foregroundRole(),
self.network.status_color(status))
except:
# dunno
pass
ssl = ' (SSL)' if status != STATUS_DISCONNECTED \
and self.network.is_ssl() else ''
self.network_status.setPalette(pal)
@ -511,8 +524,10 @@ class MainWindow(QtWidgets.QMainWindow):
else:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == next_buffer]
if index:
if len(index):
index = index[0]
else:
index = -1
if index < 0:
print('Warning: unable to find position for buffer, using end of '
'list by default')
@ -524,19 +539,30 @@ class MainWindow(QtWidgets.QMainWindow):
self.network.disconnect_weechat()
if self.network.debug_dialog:
self.network.debug_dialog.close()
config.write(self.config)
QtWidgets.QMainWindow.closeEvent(self, event)
write(self.config)
QtWidgets.QFrame.closeEvent(self, event)
def main():
app = QtWidgets.QApplication(sys.argv)
def iMain(lArgs=None):
if lArgs is None and len(sys.argv) > 1:
lArgs = sys.argv[1:]
elif lArgs is None:
lArgs = []
app = QtWidgets.QApplication(lArgs)
app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks'))
app.setWindowIcon(QtGui.QIcon(
resource_filename(__name__, 'data/icons/weechat.png')))
main_win = MainWindow()
main_win.show()
sys.exit(app.exec_())
i = app.exec_()
return i
if __name__ == '__main__':
main()
signal.signal(signal.SIGINT, signal.SIG_DFL)
try:
i = iMain()
except KeyboardInterrupt as e:
i = 0
except Exception as e:
LOG.exception(f"Exception {e}")
i = 1
sys.exit(i)

View File

@ -2,7 +2,7 @@
#
# version.py - version of QWeeChat
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#
@ -22,7 +22,7 @@
"""Version of QWeeChat."""
VERSION = '0.0.1-dev'
VERSION = '1.0.0'
def qweechat_version():

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#

View File

@ -2,7 +2,7 @@
#
# color.py - remove/replace colors in WeeChat strings
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#

View File

@ -2,7 +2,7 @@
#
# protocol.py - decode binary messages received from WeeChat/relay
#
# Copyright (C) 2011-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2011-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#

View File

@ -2,7 +2,7 @@
#
# testproto.py - command-line program for testing WeeChat/relay protocol
#
# Copyright (C) 2013-2021 Sébastien Helleu <flashcode@flashtux.org>
# Copyright (C) 2013-2022 Sébastien Helleu <flashcode@flashtux.org>
#
# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
#