Merge pull request #207 from MightyCreak/modernize-for-gtk

Modernize some GTK code
This commit is contained in:
Creak 2023-04-16 13:06:33 -04:00 committed by GitHub
commit be433eb2d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 68 deletions

View File

@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Some signals weren't properly renamed from the previous GTK3 migration (@MightyCreak)
- The syntax menu wasn't working anymore (@MightyCreak)
- Properly handles SIGINT (i.e. Ctrl+C) now (@MightyCreak)
- Add back `save_state()` to remember window's width and height (@MightyCreak)
## 0.8.1 - 2023-04-07

View File

@ -21,6 +21,7 @@ from gettext import gettext as _
from typing import Final
APP_NAME: Final[str] = 'Diffuse'
APP_ID: Final[str] = 'io.github.mightycreak.Diffuse'
COPYRIGHT: Final[str] = '''{copyright} © 2006-2019 Derrick Moser
{copyright} © 2015-2023 Romain Failliot'''.format(copyright=_("Copyright"))
WEBSITE: Final[str] = 'https://mightycreak.github.io/diffuse/'

View File

@ -22,7 +22,6 @@ import os
from gettext import gettext as _
from typing import Optional
from diffuse import constants
from diffuse import utils
import gi # type: ignore
@ -31,39 +30,6 @@ gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gtk # type: ignore # noqa: E402
# the about dialog
class AboutDialog(Gtk.AboutDialog):
def __init__(self, parent: Gtk.Widget) -> None:
Gtk.AboutDialog.__init__(self)
self.set_transient_for(parent)
self.set_logo_icon_name('io.github.mightycreak.Diffuse')
self.set_program_name(constants.APP_NAME)
self.set_version(constants.VERSION)
self.set_comments(_('Diffuse is a graphical tool for merging and comparing text files.'))
self.set_copyright(constants.COPYRIGHT)
self.set_website(constants.WEBSITE)
self.set_authors(['Derrick Moser <derrick_moser@yahoo.com>',
'Romain Failliot <romain.failliot@foolstep.com>'])
self.set_translator_credits(_('translator-credits'))
license_text = [
constants.APP_NAME + ' ' + constants.VERSION + '\n\n',
constants.COPYRIGHT + '\n\n',
'''This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.''']
self.set_license(''.join(license_text))
# custom dialogue for picking files with widgets for specifying the encoding
# and revision
class FileChooserDialog(Gtk.FileChooserDialog):

View File

@ -22,6 +22,7 @@
import os
import sys
import gettext
import signal
from gi.repository import Gio
@ -32,6 +33,10 @@ SYSCONFIGDIR = '@SYSCONFIGDIR@'
sys.path.insert(1, PKGDATADIR)
# Quietly handle SIGINT (i.e. Ctrl+C)
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Initialize i18n
gettext.bindtextdomain('diffuse', localedir=LOCALEDIR)
gettext.textdomain('diffuse')

View File

@ -37,12 +37,14 @@ class DiffuseApplication(Gtk.Application):
def __init__(self, sysconfigdir):
super().__init__(
application_id='io.github.mightycreak.Diffuse',
application_id=constants.APP_ID,
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE | Gio.ApplicationFlags.NON_UNIQUE)
self.window = None
self.sysconfigdir = sysconfigdir
self.connect('shutdown', self.on_shutdown)
self.add_main_option(
'version',
ord('v'),
@ -249,7 +251,7 @@ also retrieve revisions of files from several VCSs for comparison and merging.''
# load state
self.statepath = os.path.join(data_dir, 'state')
diff_window.loadState(self.statepath)
diff_window.load_state(self.statepath)
# process remaining command line arguments
encoding = None
@ -366,6 +368,9 @@ also retrieve revisions of files from several VCSs for comparison and merging.''
self.activate()
return 0
def on_shutdown(self, application: Gio.Application) -> None:
self.window.save_state(self.statepath)
def main(version: str, sysconfigdir: str) -> int:
"""The application's entry point."""

View File

@ -28,7 +28,7 @@ from typing import List, Optional
from urllib.parse import urlparse
from diffuse import constants, utils
from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog
from diffuse.dialogs import FileChooserDialog, NumericDialog, SearchDialog
from diffuse.preferences import Preferences
from diffuse.resources import theResources
from diffuse.utils import LineEnding
@ -682,24 +682,18 @@ class DiffuseWindow(Gtk.ApplicationWindow):
# number of created viewers (used to label some tabs)
self.viewer_count = 0
# get monitor resolution
monitor_geometry = Gdk.Display.get_default().get_monitor(0).get_geometry()
# state information that should persist across sessions
self.bool_state = {
'window_maximized': False,
'search_matchcase': False,
'search_backwards': False
}
self.int_state = {'window_width': 1024, 'window_height': 768}
self.int_state['window_x'] = max(
0,
(monitor_geometry.width - self.int_state['window_width']) / 2
)
self.int_state['window_y'] = max(
0,
(monitor_geometry.height - self.int_state['window_height']) / 2
)
self.int_state = {
'window_width': 1024,
'window_height': 768,
}
# window state signals
self.connect('configure-event', self.configure_cb)
self.connect('window-state-event', self.window_state_cb)
@ -985,37 +979,41 @@ class DiffuseWindow(Gtk.ApplicationWindow):
page.open_file(f, True)
# record the window's position and size
def configure_cb(self, widget, event):
def configure_cb(self, widget: Gtk.Widget, event: Gdk.EventConfigure) -> None:
# read the state directly instead of using window_maximized as the order
# of configure/window_state events is undefined
if (widget.get_window().get_state() & Gdk.WindowState.MAXIMIZED) == 0:
self.int_state['window_x'], self.int_state['window_y'] = widget.get_window().get_root_origin() # noqa: E501
self.int_state['window_width'] = event.width
self.int_state['window_height'] = event.height
# record the window's maximized state
def window_state_cb(self, window, event):
self.bool_state['window_maximized'] = (
(event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0
)
def window_state_cb(self, widget: Gtk.Widget, event: Gdk.EventWindowState) -> None:
is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0
self.bool_state['window_maximized'] = is_maximized
# load state information that should persist across sessions
def loadState(self, statepath: str) -> None:
def load_state(self, statepath: str) -> None:
if os.path.isfile(statepath):
try:
f = open(statepath, 'r')
ss = utils.readlines(f)
f.close()
for j, s in enumerate(ss):
try:
a = shlex.split(s, True)
if len(a) > 0:
if len(a) == 2 and a[0] in self.bool_state:
self.bool_state[a[0]] = (a[1] == 'True')
elif len(a) == 2 and a[0] in self.int_state:
self.int_state[a[0]] = int(a[1])
else:
raise ValueError()
if len(a) == 0:
continue
if len(a) != 2:
raise ValueError()
(key, value) = a
if key in self.bool_state:
self.bool_state[key] = (value == 'True')
elif key in self.int_state:
self.int_state[key] = int(value)
else:
raise ValueError()
except ValueError:
# this may happen if the state was written by a
# different version -- don't bother the user
@ -1024,13 +1022,12 @@ class DiffuseWindow(Gtk.ApplicationWindow):
# bad $HOME value? -- don't bother the user
utils.logDebug(f'Error reading {statepath}.')
self.move(self.int_state['window_x'], self.int_state['window_y'])
self.resize(self.int_state['window_width'], self.int_state['window_height'])
if self.bool_state['window_maximized']:
self.maximize()
# save state information that should persist across sessions
def saveState(self, statepath: str) -> None:
def save_state(self, statepath: str) -> None:
try:
ss = []
for k, v in self.bool_state.items():
@ -1038,6 +1035,7 @@ class DiffuseWindow(Gtk.ApplicationWindow):
for k, v in self.int_state.items():
ss.append(f'{k} {v}\n')
ss.sort()
f = open(statepath, 'w')
f.write(f'# This state file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n') # noqa: E501
for s in ss:
@ -1733,9 +1731,41 @@ class DiffuseWindow(Gtk.ApplicationWindow):
# callback for the about menu item
def about_cb(self, widget, data):
dialog = AboutDialog(self.get_toplevel())
dialog.run()
dialog.destroy()
authors = [
'Derrick Moser <derrick_moser@yahoo.com>',
'Romain Failliot <romain.failliot@foolstep.com>'
]
license = f'''{constants.APP_NAME} {constants.VERSION}
{constants.COPYRIGHT}
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.'''
dialog = Gtk.AboutDialog(
transient_for=self.get_toplevel(),
modal=True,
program_name=constants.APP_NAME,
logo_icon_name=constants.APP_ID,
version=constants.VERSION,
comments=_('Diffuse is a graphical tool for merging and comparing text files.'),
copyright=constants.COPYRIGHT,
website=constants.WEBSITE,
authors=authors,
translator_credits=_('translator-credits'),
license=license)
dialog.present()
def _append_buttons(box, size, specs):