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
|
||||
uses: cclauss/GitHub-Action-for-pylint@master
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -100,6 +100,7 @@ disable=raw-checker-failed,
|
|||
too-many-arguments,
|
||||
too-many-branches,
|
||||
too-many-instance-attributes,
|
||||
too-many-lines,
|
||||
too-many-locals,
|
||||
too-many-nested-blocks,
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
'__init__.py',
|
||||
'dialogs.py',
|
||||
'main.py',
|
||||
'preferences.py',
|
||||
'resources.py',
|
||||
'utils.py',
|
||||
'widgets.py',
|
||||
]
|
||||
|
||||
install_data(diffuse_sources, install_dir: moduledir)
|
||||
|
|
|
@ -535,3 +535,5 @@ class _SyntaxParser:
|
|||
blocks.append([start, end, token_type])
|
||||
start = end
|
||||
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
|
||||
|
||||
from diffuse import constants
|
||||
from diffuse.resources import theResources
|
||||
|
||||
# convenience class for displaying a message dialogue
|
||||
class MessageDialog(Gtk.MessageDialog):
|
||||
|
@ -155,8 +156,7 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
|
|||
prefs.convertToNativePath('/bin/bash.exe'),
|
||||
'-l',
|
||||
'-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
|
||||
# use subprocess.Popen to retrieve the file contents
|
||||
if isWindows():
|
||||
|
@ -264,6 +264,67 @@ def null_to_empty(s):
|
|||
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
|
||||
# such as icon and help documentation
|
||||
|
@ -279,9 +340,9 @@ lang = locale.getdefaultlocale()[0]
|
|||
if isWindows():
|
||||
# gettext looks for the language using environment variables which
|
||||
# are normally not set on Windows so we try setting it for them
|
||||
for v in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
||||
if v in os.environ:
|
||||
lang = os.environ[v]
|
||||
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
|
||||
if lang_env in os.environ:
|
||||
lang = os.environ[lang_env]
|
||||
# remove any additional languages, encodings, or modifications
|
||||
for c in ':.@':
|
||||
lang = lang.split(c)[0]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue