Merge pull request #119 from MightyCreak/pylint
Extract dialogs and widgets into their own modules
This commit is contained in:
commit
27769eed56
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- name: Pylint
|
- name: Pylint
|
||||||
uses: cclauss/GitHub-Action-for-pylint@master
|
uses: cclauss/GitHub-Action-for-pylint@master
|
||||||
with:
|
with:
|
||||||
args: "pylint src/utils.py src/vcs/"
|
args: "pylint src/vcs/ src/dialogs.py src/preferences.py src/resources.py src/utils.py src/widgets.py"
|
||||||
|
|
||||||
meson-build-test:
|
meson-build-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -100,6 +100,7 @@ disable=raw-checker-failed,
|
||||||
too-many-arguments,
|
too-many-arguments,
|
||||||
too-many-branches,
|
too-many-branches,
|
||||||
too-many-instance-attributes,
|
too-many-instance-attributes,
|
||||||
|
too-many-lines,
|
||||||
too-many-locals,
|
too-many-locals,
|
||||||
too-many-nested-blocks,
|
too-many-nested-blocks,
|
||||||
too-many-statements,
|
too-many-statements,
|
||||||
|
|
|
@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added a flatpak job in the CI
|
- Added a flatpak job in the CI
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- main.py slimmed down by about 1000 lines
|
- main.py slimmed down by about 5000 lines
|
||||||
|
- The new widgets.py is a bit fat though (~4000 lines)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
# Diffuse: a graphical tool for merging and comparing text files.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
|
||||||
|
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# pylint: disable=wrong-import-position
|
||||||
|
import gi
|
||||||
|
gi.require_version('GObject', '2.0')
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import GObject, Gtk
|
||||||
|
# pylint: enable=wrong-import-position
|
||||||
|
|
||||||
|
from diffuse import constants
|
||||||
|
from diffuse import utils
|
||||||
|
|
||||||
|
# the about dialog
|
||||||
|
class AboutDialog(Gtk.AboutDialog):
|
||||||
|
def __init__(self):
|
||||||
|
Gtk.AboutDialog.__init__(self)
|
||||||
|
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):
|
||||||
|
# record last chosen folder so the file chooser can start at a more useful
|
||||||
|
# location for empty panes
|
||||||
|
last_chosen_folder = os.path.realpath(os.curdir)
|
||||||
|
|
||||||
|
def __current_folder_changed_cb(self, widget):
|
||||||
|
FileChooserDialog.last_chosen_folder = widget.get_current_folder()
|
||||||
|
|
||||||
|
def __init__(self, title, parent, prefs, action, accept, rev=False):
|
||||||
|
Gtk.FileChooserDialog.__init__(self, title=title, parent=parent, action=action)
|
||||||
|
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
|
||||||
|
self.add_button(accept, Gtk.ResponseType.OK)
|
||||||
|
self.prefs = prefs
|
||||||
|
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
|
||||||
|
hbox.set_border_width(5)
|
||||||
|
label = Gtk.Label.new(_('Encoding: '))
|
||||||
|
hbox.pack_start(label, False, False, 0)
|
||||||
|
label.show()
|
||||||
|
self.encoding = entry = utils.EncodingMenu(
|
||||||
|
prefs,
|
||||||
|
action in [ Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER ])
|
||||||
|
hbox.pack_start(entry, False, False, 5)
|
||||||
|
entry.show()
|
||||||
|
if rev:
|
||||||
|
self.revision = entry = Gtk.Entry.new()
|
||||||
|
hbox.pack_end(entry, False, False, 0)
|
||||||
|
entry.show()
|
||||||
|
label = Gtk.Label.new(_('Revision: '))
|
||||||
|
hbox.pack_end(label, False, False, 0)
|
||||||
|
label.show()
|
||||||
|
|
||||||
|
self.vbox.pack_start(hbox, False, False, 0) # pylint: disable=no-member
|
||||||
|
hbox.show()
|
||||||
|
self.set_current_folder(self.last_chosen_folder)
|
||||||
|
self.connect('current-folder-changed', self.__current_folder_changed_cb)
|
||||||
|
|
||||||
|
def set_encoding(self, encoding):
|
||||||
|
self.encoding.set_text(encoding)
|
||||||
|
|
||||||
|
def get_encoding(self):
|
||||||
|
return self.encoding.get_text()
|
||||||
|
|
||||||
|
def get_revision(self):
|
||||||
|
return self.revision.get_text()
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
# convert from UTF-8 string to unicode
|
||||||
|
return Gtk.FileChooserDialog.get_filename(self) # pylint: disable=no-member
|
||||||
|
|
||||||
|
# dialogue used to search for text
|
||||||
|
class NumericDialog(Gtk.Dialog):
|
||||||
|
def __init__(self, parent, title, text, val, lower, upper, step=1, page=0):
|
||||||
|
Gtk.Dialog.__init__(self, title=title, parent=parent, destroy_with_parent=True)
|
||||||
|
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
|
||||||
|
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
|
||||||
|
|
||||||
|
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
|
||||||
|
vbox.set_border_width(10)
|
||||||
|
|
||||||
|
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
|
||||||
|
label = Gtk.Label.new(text)
|
||||||
|
hbox.pack_start(label, False, False, 0)
|
||||||
|
label.show()
|
||||||
|
adj = Gtk.Adjustment.new(val, lower, upper, step, page, 0)
|
||||||
|
self.button = button = Gtk.SpinButton.new(adj, 1.0, 0)
|
||||||
|
button.connect('activate', self.button_cb)
|
||||||
|
hbox.pack_start(button, True, True, 0)
|
||||||
|
button.show()
|
||||||
|
|
||||||
|
vbox.pack_start(hbox, True, True, 0)
|
||||||
|
hbox.show()
|
||||||
|
|
||||||
|
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member
|
||||||
|
vbox.show()
|
||||||
|
|
||||||
|
def button_cb(self, widget):
|
||||||
|
self.response(Gtk.ResponseType.ACCEPT)
|
||||||
|
|
||||||
|
# dialogue used to search for text
|
||||||
|
class SearchDialog(Gtk.Dialog):
|
||||||
|
def __init__(self, parent, pattern=None, history=None):
|
||||||
|
Gtk.Dialog.__init__(self, title=_('Find...'), parent=parent, destroy_with_parent=True)
|
||||||
|
self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
|
||||||
|
self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
|
||||||
|
|
||||||
|
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
|
||||||
|
vbox.set_border_width(10)
|
||||||
|
|
||||||
|
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
|
||||||
|
label = Gtk.Label.new(_('Search For: '))
|
||||||
|
hbox.pack_start(label, False, False, 0)
|
||||||
|
label.show()
|
||||||
|
combo = Gtk.ComboBoxText.new_with_entry()
|
||||||
|
self.entry = combo.get_child()
|
||||||
|
self.entry.connect('activate', self.entry_cb)
|
||||||
|
|
||||||
|
if pattern is not None:
|
||||||
|
self.entry.set_text(pattern)
|
||||||
|
|
||||||
|
if history is not None:
|
||||||
|
completion = Gtk.EntryCompletion.new()
|
||||||
|
liststore = Gtk.ListStore(GObject.TYPE_STRING)
|
||||||
|
completion.set_model(liststore)
|
||||||
|
completion.set_text_column(0)
|
||||||
|
for h in history:
|
||||||
|
liststore.append([h])
|
||||||
|
combo.append_text(h)
|
||||||
|
self.entry.set_completion(completion)
|
||||||
|
|
||||||
|
hbox.pack_start(combo, True, True, 0)
|
||||||
|
combo.show()
|
||||||
|
vbox.pack_start(hbox, False, False, 0)
|
||||||
|
hbox.show()
|
||||||
|
|
||||||
|
button = Gtk.CheckButton.new_with_mnemonic(_('Match Case'))
|
||||||
|
self.match_case_button = button
|
||||||
|
vbox.pack_start(button, False, False, 0)
|
||||||
|
button.show()
|
||||||
|
|
||||||
|
button = Gtk.CheckButton.new_with_mnemonic(_('Search Backwards'))
|
||||||
|
self.backwards_button = button
|
||||||
|
vbox.pack_start(button, False, False, 0)
|
||||||
|
button.show()
|
||||||
|
|
||||||
|
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member
|
||||||
|
vbox.show()
|
||||||
|
|
||||||
|
# callback used when the Enter key is pressed
|
||||||
|
def entry_cb(self, widget):
|
||||||
|
self.response(Gtk.ResponseType.ACCEPT)
|
4361
src/main.py
4361
src/main.py
File diff suppressed because it is too large
Load Diff
|
@ -32,10 +32,12 @@ configure_file(
|
||||||
|
|
||||||
diffuse_sources = [
|
diffuse_sources = [
|
||||||
'__init__.py',
|
'__init__.py',
|
||||||
|
'dialogs.py',
|
||||||
'main.py',
|
'main.py',
|
||||||
'preferences.py',
|
'preferences.py',
|
||||||
'resources.py',
|
'resources.py',
|
||||||
'utils.py',
|
'utils.py',
|
||||||
|
'widgets.py',
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(diffuse_sources, install_dir: moduledir)
|
install_data(diffuse_sources, install_dir: moduledir)
|
||||||
|
|
|
@ -535,3 +535,5 @@ class _SyntaxParser:
|
||||||
blocks.append([start, end, token_type])
|
blocks.append([start, end, token_type])
|
||||||
start = end
|
start = end
|
||||||
return state_name, blocks
|
return state_name, blocks
|
||||||
|
|
||||||
|
theResources = Resources()
|
||||||
|
|
71
src/utils.py
71
src/utils.py
|
@ -30,6 +30,7 @@ from gi.repository import Gtk
|
||||||
# pylint: enable=wrong-import-position
|
# pylint: enable=wrong-import-position
|
||||||
|
|
||||||
from diffuse import constants
|
from diffuse import constants
|
||||||
|
from diffuse.resources import theResources
|
||||||
|
|
||||||
# convenience class for displaying a message dialogue
|
# convenience class for displaying a message dialogue
|
||||||
class MessageDialog(Gtk.MessageDialog):
|
class MessageDialog(Gtk.MessageDialog):
|
||||||
|
@ -155,8 +156,7 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
|
||||||
prefs.convertToNativePath('/bin/bash.exe'),
|
prefs.convertToNativePath('/bin/bash.exe'),
|
||||||
'-l',
|
'-l',
|
||||||
'-c',
|
'-c',
|
||||||
f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}"
|
f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}" ]
|
||||||
]
|
|
||||||
dn = None
|
dn = None
|
||||||
# use subprocess.Popen to retrieve the file contents
|
# use subprocess.Popen to retrieve the file contents
|
||||||
if isWindows():
|
if isWindows():
|
||||||
|
@ -264,6 +264,67 @@ def null_to_empty(s):
|
||||||
s = ''
|
s = ''
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
# utility method to step advance an adjustment
|
||||||
|
def step_adjustment(adj, delta):
|
||||||
|
v = adj.get_value() + delta
|
||||||
|
# clamp to the allowed range
|
||||||
|
v = max(v, int(adj.get_lower()))
|
||||||
|
v = min(v, int(adj.get_upper() - adj.get_page_size()))
|
||||||
|
adj.set_value(v)
|
||||||
|
|
||||||
|
# convenience method for creating a menu according to a template
|
||||||
|
def createMenu(specs, radio=None, accel_group=None):
|
||||||
|
menu = Gtk.Menu.new()
|
||||||
|
for spec in specs:
|
||||||
|
if len(spec) > 0:
|
||||||
|
if len(spec) > 7 and spec[7] is not None:
|
||||||
|
g, k = spec[7]
|
||||||
|
if g not in radio:
|
||||||
|
item = Gtk.RadioMenuItem.new_with_mnemonic_from_widget(None, spec[0])
|
||||||
|
radio[g] = (item, {})
|
||||||
|
else:
|
||||||
|
item = Gtk.RadioMenuItem.new_with_mnemonic_from_widget(radio[g][0], spec[0])
|
||||||
|
radio[g][1][k] = item
|
||||||
|
else:
|
||||||
|
item = Gtk.ImageMenuItem.new_with_mnemonic(spec[0])
|
||||||
|
cb = spec[1]
|
||||||
|
if cb is not None:
|
||||||
|
data = spec[2]
|
||||||
|
item.connect('activate', cb, data)
|
||||||
|
if len(spec) > 3 and spec[3] is not None:
|
||||||
|
image = Gtk.Image.new()
|
||||||
|
image.set_from_stock(spec[3], Gtk.IconSize.MENU) # pylint: disable=no-member
|
||||||
|
item.set_image(image)
|
||||||
|
if accel_group is not None and len(spec) > 4:
|
||||||
|
a = theResources.getKeyBindings('menu', spec[4])
|
||||||
|
if len(a) > 0:
|
||||||
|
key, modifier = a[0]
|
||||||
|
item.add_accelerator(
|
||||||
|
'activate',
|
||||||
|
accel_group,
|
||||||
|
key,
|
||||||
|
modifier,
|
||||||
|
Gtk.AccelFlags.VISIBLE)
|
||||||
|
if len(spec) > 5:
|
||||||
|
item.set_sensitive(spec[5])
|
||||||
|
if len(spec) > 6 and spec[6] is not None:
|
||||||
|
item.set_submenu(createMenu(spec[6], radio, accel_group))
|
||||||
|
item.set_use_underline(True)
|
||||||
|
else:
|
||||||
|
item = Gtk.SeparatorMenuItem.new()
|
||||||
|
item.show()
|
||||||
|
menu.append(item)
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
# masks used to indicate the presence of particular line endings
|
||||||
|
DOS_FORMAT = 1
|
||||||
|
MAC_FORMAT = 2
|
||||||
|
UNIX_FORMAT = 4
|
||||||
|
|
||||||
|
# avoid some dictionary lookups when string.whitespace is used in loops
|
||||||
|
# this is sorted based upon frequency to speed up code for stripping whitespace
|
||||||
|
whitespace = ' \t\n\r\x0b\x0c'
|
||||||
|
|
||||||
# use the program's location as a starting place to search for supporting files
|
# use the program's location as a starting place to search for supporting files
|
||||||
# such as icon and help documentation
|
# such as icon and help documentation
|
||||||
|
@ -279,9 +340,9 @@ lang = locale.getdefaultlocale()[0]
|
||||||
if isWindows():
|
if isWindows():
|
||||||
# gettext looks for the language using environment variables which
|
# gettext looks for the language using environment variables which
|
||||||
# are normally not set on Windows so we try setting it for them
|
# are normally not set on Windows so we try setting it for them
|
||||||
for v in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
||||||
if v in os.environ:
|
if lang_env in os.environ:
|
||||||
lang = os.environ[v]
|
lang = os.environ[lang_env]
|
||||||
# remove any additional languages, encodings, or modifications
|
# remove any additional languages, encodings, or modifications
|
||||||
for c in ':.@':
|
for c in ':.@':
|
||||||
lang = lang.split(c)[0]
|
lang = lang.split(c)[0]
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue