Merge pull request #207 from MightyCreak/modernize-for-gtk
Modernize some GTK code
This commit is contained in:
commit
be433eb2d7
|
@ -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)
|
- Some signals weren't properly renamed from the previous GTK3 migration (@MightyCreak)
|
||||||
- The syntax menu wasn't working anymore (@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
|
## 0.8.1 - 2023-04-07
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ from gettext import gettext as _
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
APP_NAME: Final[str] = 'Diffuse'
|
APP_NAME: Final[str] = 'Diffuse'
|
||||||
|
APP_ID: Final[str] = 'io.github.mightycreak.Diffuse'
|
||||||
COPYRIGHT: Final[str] = '''{copyright} © 2006-2019 Derrick Moser
|
COPYRIGHT: Final[str] = '''{copyright} © 2006-2019 Derrick Moser
|
||||||
{copyright} © 2015-2023 Romain Failliot'''.format(copyright=_("Copyright"))
|
{copyright} © 2015-2023 Romain Failliot'''.format(copyright=_("Copyright"))
|
||||||
WEBSITE: Final[str] = 'https://mightycreak.github.io/diffuse/'
|
WEBSITE: Final[str] = 'https://mightycreak.github.io/diffuse/'
|
||||||
|
|
|
@ -22,7 +22,6 @@ import os
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from diffuse import constants
|
|
||||||
from diffuse import utils
|
from diffuse import utils
|
||||||
|
|
||||||
import gi # type: ignore
|
import gi # type: ignore
|
||||||
|
@ -31,39 +30,6 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import GObject, Gtk # type: ignore # noqa: E402
|
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
|
# custom dialogue for picking files with widgets for specifying the encoding
|
||||||
# and revision
|
# and revision
|
||||||
class FileChooserDialog(Gtk.FileChooserDialog):
|
class FileChooserDialog(Gtk.FileChooserDialog):
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import gettext
|
import gettext
|
||||||
|
import signal
|
||||||
|
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
|
|
||||||
|
@ -32,6 +33,10 @@ SYSCONFIGDIR = '@SYSCONFIGDIR@'
|
||||||
|
|
||||||
sys.path.insert(1, PKGDATADIR)
|
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.bindtextdomain('diffuse', localedir=LOCALEDIR)
|
||||||
gettext.textdomain('diffuse')
|
gettext.textdomain('diffuse')
|
||||||
|
|
||||||
|
|
|
@ -37,12 +37,14 @@ class DiffuseApplication(Gtk.Application):
|
||||||
|
|
||||||
def __init__(self, sysconfigdir):
|
def __init__(self, sysconfigdir):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
application_id='io.github.mightycreak.Diffuse',
|
application_id=constants.APP_ID,
|
||||||
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE | Gio.ApplicationFlags.NON_UNIQUE)
|
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE | Gio.ApplicationFlags.NON_UNIQUE)
|
||||||
|
|
||||||
self.window = None
|
self.window = None
|
||||||
self.sysconfigdir = sysconfigdir
|
self.sysconfigdir = sysconfigdir
|
||||||
|
|
||||||
|
self.connect('shutdown', self.on_shutdown)
|
||||||
|
|
||||||
self.add_main_option(
|
self.add_main_option(
|
||||||
'version',
|
'version',
|
||||||
ord('v'),
|
ord('v'),
|
||||||
|
@ -249,7 +251,7 @@ also retrieve revisions of files from several VCSs for comparison and merging.''
|
||||||
|
|
||||||
# load state
|
# load state
|
||||||
self.statepath = os.path.join(data_dir, '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
|
# process remaining command line arguments
|
||||||
encoding = None
|
encoding = None
|
||||||
|
@ -366,6 +368,9 @@ also retrieve revisions of files from several VCSs for comparison and merging.''
|
||||||
self.activate()
|
self.activate()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def on_shutdown(self, application: Gio.Application) -> None:
|
||||||
|
self.window.save_state(self.statepath)
|
||||||
|
|
||||||
|
|
||||||
def main(version: str, sysconfigdir: str) -> int:
|
def main(version: str, sysconfigdir: str) -> int:
|
||||||
"""The application's entry point."""
|
"""The application's entry point."""
|
||||||
|
|
|
@ -28,7 +28,7 @@ from typing import List, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from diffuse import constants, utils
|
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.preferences import Preferences
|
||||||
from diffuse.resources import theResources
|
from diffuse.resources import theResources
|
||||||
from diffuse.utils import LineEnding
|
from diffuse.utils import LineEnding
|
||||||
|
@ -682,24 +682,18 @@ class DiffuseWindow(Gtk.ApplicationWindow):
|
||||||
# number of created viewers (used to label some tabs)
|
# number of created viewers (used to label some tabs)
|
||||||
self.viewer_count = 0
|
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
|
# state information that should persist across sessions
|
||||||
self.bool_state = {
|
self.bool_state = {
|
||||||
'window_maximized': False,
|
'window_maximized': False,
|
||||||
'search_matchcase': False,
|
'search_matchcase': False,
|
||||||
'search_backwards': False
|
'search_backwards': False
|
||||||
}
|
}
|
||||||
self.int_state = {'window_width': 1024, 'window_height': 768}
|
self.int_state = {
|
||||||
self.int_state['window_x'] = max(
|
'window_width': 1024,
|
||||||
0,
|
'window_height': 768,
|
||||||
(monitor_geometry.width - self.int_state['window_width']) / 2
|
}
|
||||||
)
|
|
||||||
self.int_state['window_y'] = max(
|
# window state signals
|
||||||
0,
|
|
||||||
(monitor_geometry.height - self.int_state['window_height']) / 2
|
|
||||||
)
|
|
||||||
self.connect('configure-event', self.configure_cb)
|
self.connect('configure-event', self.configure_cb)
|
||||||
self.connect('window-state-event', self.window_state_cb)
|
self.connect('window-state-event', self.window_state_cb)
|
||||||
|
|
||||||
|
@ -985,35 +979,39 @@ class DiffuseWindow(Gtk.ApplicationWindow):
|
||||||
page.open_file(f, True)
|
page.open_file(f, True)
|
||||||
|
|
||||||
# record the window's position and size
|
# 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
|
# read the state directly instead of using window_maximized as the order
|
||||||
# of configure/window_state events is undefined
|
# of configure/window_state events is undefined
|
||||||
if (widget.get_window().get_state() & Gdk.WindowState.MAXIMIZED) == 0:
|
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_width'] = event.width
|
||||||
self.int_state['window_height'] = event.height
|
self.int_state['window_height'] = event.height
|
||||||
|
|
||||||
# record the window's maximized state
|
# record the window's maximized state
|
||||||
def window_state_cb(self, window, event):
|
def window_state_cb(self, widget: Gtk.Widget, event: Gdk.EventWindowState) -> None:
|
||||||
self.bool_state['window_maximized'] = (
|
is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0
|
||||||
(event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0
|
self.bool_state['window_maximized'] = is_maximized
|
||||||
)
|
|
||||||
|
|
||||||
# load state information that should persist across sessions
|
# 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):
|
if os.path.isfile(statepath):
|
||||||
try:
|
try:
|
||||||
f = open(statepath, 'r')
|
f = open(statepath, 'r')
|
||||||
ss = utils.readlines(f)
|
ss = utils.readlines(f)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
for j, s in enumerate(ss):
|
for j, s in enumerate(ss):
|
||||||
try:
|
try:
|
||||||
a = shlex.split(s, True)
|
a = shlex.split(s, True)
|
||||||
if len(a) > 0:
|
if len(a) == 0:
|
||||||
if len(a) == 2 and a[0] in self.bool_state:
|
continue
|
||||||
self.bool_state[a[0]] = (a[1] == 'True')
|
if len(a) != 2:
|
||||||
elif len(a) == 2 and a[0] in self.int_state:
|
raise ValueError()
|
||||||
self.int_state[a[0]] = int(a[1])
|
|
||||||
|
(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:
|
else:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -1024,13 +1022,12 @@ class DiffuseWindow(Gtk.ApplicationWindow):
|
||||||
# bad $HOME value? -- don't bother the user
|
# bad $HOME value? -- don't bother the user
|
||||||
utils.logDebug(f'Error reading {statepath}.')
|
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'])
|
self.resize(self.int_state['window_width'], self.int_state['window_height'])
|
||||||
if self.bool_state['window_maximized']:
|
if self.bool_state['window_maximized']:
|
||||||
self.maximize()
|
self.maximize()
|
||||||
|
|
||||||
# save state information that should persist across sessions
|
# save state information that should persist across sessions
|
||||||
def saveState(self, statepath: str) -> None:
|
def save_state(self, statepath: str) -> None:
|
||||||
try:
|
try:
|
||||||
ss = []
|
ss = []
|
||||||
for k, v in self.bool_state.items():
|
for k, v in self.bool_state.items():
|
||||||
|
@ -1038,6 +1035,7 @@ class DiffuseWindow(Gtk.ApplicationWindow):
|
||||||
for k, v in self.int_state.items():
|
for k, v in self.int_state.items():
|
||||||
ss.append(f'{k} {v}\n')
|
ss.append(f'{k} {v}\n')
|
||||||
ss.sort()
|
ss.sort()
|
||||||
|
|
||||||
f = open(statepath, 'w')
|
f = open(statepath, 'w')
|
||||||
f.write(f'# This state file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n') # noqa: E501
|
f.write(f'# This state file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n') # noqa: E501
|
||||||
for s in ss:
|
for s in ss:
|
||||||
|
@ -1733,9 +1731,41 @@ class DiffuseWindow(Gtk.ApplicationWindow):
|
||||||
|
|
||||||
# callback for the about menu item
|
# callback for the about menu item
|
||||||
def about_cb(self, widget, data):
|
def about_cb(self, widget, data):
|
||||||
dialog = AboutDialog(self.get_toplevel())
|
authors = [
|
||||||
dialog.run()
|
'Derrick Moser <derrick_moser@yahoo.com>',
|
||||||
dialog.destroy()
|
'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):
|
def _append_buttons(box, size, specs):
|
||||||
|
|
Loading…
Reference in New Issue