Merge pull request #119 from MightyCreak/pylint

Extract dialogs and widgets into their own modules
This commit is contained in:
Creak 2021-11-21 02:31:24 -05:00 committed by GitHub
commit 27769eed56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 4435 additions and 4274 deletions

View File

@ -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

View File

@ -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,

View File

@ -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

194
src/dialogs.py Normal file
View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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()

View File

@ -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]

4073
src/widgets.py Normal file

File diff suppressed because it is too large Load Diff