Skip to content

Commit

Permalink
Add options dialog with simple configuration management.
Browse files Browse the repository at this point in the history
Minor presentation bugfixes.
Bump version and bdbag dependency version.
  • Loading branch information
mikedarcy committed Mar 26, 2018
1 parent cb86c88 commit 4cae06b
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 48 deletions.
Binary file modified bdbag_gui/images/bdbag_gui.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion bdbag_gui/impl/async_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ def run(self):
requester.Success.emit(self.uid, result)
except:
(etype, value, traceback) = sys.exc_info()
sys.excepthook(etype, value, traceback)
if self.cancelled:
return
requester.Error.emit(self.uid, str(value))
Expand Down
46 changes: 24 additions & 22 deletions bdbag_gui/impl/bag_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def init_request(self):


class BagCreateOrUpdateTask(BagTask):
status_update_signal = pyqtSignal(bool, str)
status_update_signal = pyqtSignal(str, bool, bool)

def __init__(self, parent=None):
super(BagCreateOrUpdateTask, self).__init__(parent)
Expand All @@ -28,38 +28,40 @@ def __init__(self, parent=None):
def success_callback(self, rid, result):
if rid != self.rid:
return
self.status_update_signal.emit(True, "Bag %s completed successfully" % ("update" if self.update else "creation"))
self.status_update_signal.emit("Bag %s completed successfully" % ("update" if self.update else "creation"),
True, self.update)

def error_callback(self, rid, error):
if rid != self.rid:
return
self.status_update_signal.emit(False, "Bag %s error: %s" % ("update" if self.update else "creation", error))
self.status_update_signal.emit("Bag %s error: %s" % ("update" if self.update else "creation", error),
False, self.update)

def createOrUpdate(self, bagPath, update):
def createOrUpdate(self, bagPath, update, config_file):
self.update = update
self.init_request()
self.request = async_execute(bdb.make_bag,
[bagPath, ['md5', 'sha256'], update],
[bagPath, ['md5', 'sha256'], update, True, False, None, None, None, config_file],
self.rid,
self.success_callback,
self.error_callback)


class BagRevertTask(BagTask):
status_update_signal = pyqtSignal(bool, str)
status_update_signal = pyqtSignal(str, bool)

def __init__(self, parent=None):
super(BagRevertTask, self).__init__(parent)

def success_callback(self, rid, result):
if rid != self.rid:
return
self.status_update_signal.emit(False, "Bag reverted successfully")
self.status_update_signal.emit("Bag reverted successfully", True)

def error_callback(self, rid, error):
if rid != self.rid:
return
self.status_update_signal.emit(True, "Bag reversion failed")
self.status_update_signal.emit("Bag reversion failed", False)

def revert(self, bagPath):
self.init_request()
Expand All @@ -72,7 +74,7 @@ def revert(self, bagPath):

class BagValidateTask(BagTask):

status_update_signal = pyqtSignal(str)
status_update_signal = pyqtSignal(str, bool)
progress_update_signal = pyqtSignal(int, int)

def __init__(self, parent=None):
Expand All @@ -81,12 +83,12 @@ def __init__(self, parent=None):
def success_callback(self, rid, result):
if rid != self.rid:
return
self.status_update_signal.emit("Bag validation complete")
self.status_update_signal.emit("Bag validation complete", True)

def error_callback(self, rid, error):
if rid != self.rid:
return
self.status_update_signal.emit("Bag validation error: %s" % error)
self.status_update_signal.emit("Bag validation error: %s" % error, False)

def progress_callback(self, current, maximum):
if self.request.cancelled:
Expand All @@ -95,18 +97,18 @@ def progress_callback(self, current, maximum):
self.progress_update_signal.emit(current, maximum)
return True

def validate(self, bagPath, fast):
def validate(self, bagPath, fast, config_file):
self.init_request()
self.request = async_execute(bdb.validate_bag,
[bagPath, fast, self.progress_callback],
[bagPath, fast, self.progress_callback, config_file],
self.rid,
self.success_callback,
self.error_callback)


class BagFetchTask(BagTask):

status_update_signal = pyqtSignal(str)
status_update_signal = pyqtSignal(str, bool)
progress_update_signal = pyqtSignal(int, int)

def __init__(self, parent=None):
Expand All @@ -115,13 +117,13 @@ def __init__(self, parent=None):
def success_callback(self, rid, result):
if rid != self.rid:
return
self.status_update_signal.emit("Bag fetch complete: All file references resolved successfully"
if result else "Bag fetch incomplete: Some file references were not resolved")
self.status_update_signal.emit("Bag fetch complete: All file references resolved successfully" if result else
"Bag fetch incomplete: Some file references were not resolved", True)

def error_callback(self, rid, error):
if rid != self.rid:
return
self.status_update_signal.emit("Bag fetch error: %s" % error)
self.status_update_signal.emit("Bag fetch error: %s" % error, False)

def progress_callback(self, current, maximum):
if self.request.cancelled:
Expand All @@ -130,30 +132,30 @@ def progress_callback(self, current, maximum):
self.progress_update_signal.emit(current, maximum)
return True

def fetch(self, bagPath, allRefs):
def fetch(self, bagPath, allRefs, keychainFile, configFile):
self.init_request()
self.request = async_execute(bdb.resolve_fetch,
[bagPath, allRefs, self.progress_callback],
[bagPath, allRefs, self.progress_callback, keychainFile, configFile],
self.rid,
self.success_callback,
self.error_callback)


class BagArchiveTask(BagTask):
status_update_signal = pyqtSignal(str)
status_update_signal = pyqtSignal(str, bool)

def __init__(self, parent=None):
super(BagArchiveTask, self).__init__(parent)

def success_callback(self, rid, result):
if rid != self.rid:
return
self.status_update_signal.emit("Bag archive complete")
self.status_update_signal.emit("Bag archive complete", True)

def error_callback(self, rid, error):
if rid != self.rid:
return
self.status_update_signal.emit("Bag archive error: %s" % error)
self.status_update_signal.emit("Bag archive error: %s" % error, False)

def archive(self, bagPath, archiver):
self.init_request()
Expand Down
207 changes: 207 additions & 0 deletions bdbag_gui/ui/json_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import sys

from PyQt5.QtCore import Qt, QRegExp, QSize, QRect
from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat, QFont, QPainter, QColor, QTextFormat
from PyQt5.QtWidgets import QTextEdit, QApplication, QWidget, QPlainTextEdit, QMessageBox, QDialog, QVBoxLayout


class JSONSyntaxHighlighter(QSyntaxHighlighter):
def __init__(self, parent=None):
super(JSONSyntaxHighlighter, self).__init__(parent)

self.symbol_format = QTextCharFormat()
self.symbol_format.setForeground(Qt.blue)
self.symbol_format.setFontWeight(QFont.Bold)

self.name_format = QTextCharFormat()
self.name_format.setForeground(Qt.darkMagenta)
self.name_format.setFontWeight(QFont.Bold)
self.name_format.setFontItalic(True)

self.value_format = QTextCharFormat()
self.value_format.setForeground(Qt.darkGreen)

def highlightBlock(self, text):
expression = QRegExp("(\\{|\\}|\\[|\\]|\\:|\\,)")
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, self.symbol_format)
index = expression.indexIn(text, index + length)

text.replace("\\\"", " ")

expression = QRegExp("\".*\" *\\:")
expression.setMinimal(True)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length - 1, self.name_format)
index = expression.indexIn(text, index + length)

expression = QRegExp("\\: *\".*\"")
expression.setMinimal(True)
index = expression.indexIn(text)
while index >= 0:
length = expression.matchedLength()
self.setFormat(index, length, self.value_format)
index = expression.indexIn(text, index + length)


class LineNumberArea(QWidget):

def __init__(self, editor):
super().__init__(editor)
self.editor = editor

def sizeHint(self):
return QSize(self.editor.lineNumberAreaWidth(), 0)

def paintEvent(self, event):
self.editor.lineNumberAreaPaintEvent(event)


class CodeEditor(QPlainTextEdit):
def __init__(self):
super().__init__()
self.modified = False
self.lineNumberArea = LineNumberArea(self)

self.modificationChanged.connect(self.setModified)
self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
self.updateRequest.connect(self.updateLineNumberArea)
self.cursorPositionChanged.connect(self.highlightCurrentLine)

self.updateLineNumberAreaWidth(0)

def setModified(self, modified):
self.modified = modified

def lineNumberAreaWidth(self):
digits = 1
count = max(1, self.blockCount())
while count >= 10:
count /= 10
digits += 1
space = 3 + self.fontMetrics().width('9') * digits
return space

def updateLineNumberAreaWidth(self, _):
self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)

def updateLineNumberArea(self, rect, dy):

if dy:
self.lineNumberArea.scroll(0, dy)
else:
self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())

if rect.contains(self.viewport().rect()):
self.updateLineNumberAreaWidth(0)

def resizeEvent(self, event):
super().resizeEvent(event)

cr = self.contentsRect()
self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))

def lineNumberAreaPaintEvent(self, event):
painter = QPainter(self.lineNumberArea)

painter.fillRect(event.rect(), Qt.lightGray)

block = self.firstVisibleBlock()
blockNumber = block.blockNumber()
top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
bottom = top + self.blockBoundingRect(block).height()

height = self.fontMetrics().height()
while block.isValid() and (top <= event.rect().bottom()):
if block.isVisible() and (bottom >= event.rect().top()):
number = str(blockNumber + 1)
painter.setPen(Qt.black)
painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignRight, number)

block = block.next()
top = bottom
bottom = top + self.blockBoundingRect(block).height()
blockNumber += 1

def highlightCurrentLine(self):
extraSelections = []

if not self.isReadOnly():
selection = QTextEdit.ExtraSelection()
lineColor = QColor(Qt.yellow).lighter(160)
selection.format.setBackground(lineColor)
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
selection.cursor = self.textCursor()
selection.cursor.clearSelection()
extraSelections.append(selection)

self.setExtraSelections(extraSelections)


class JSONEditor(QDialog):
def __init__(self, parent, file_path, title="JSON Editor"):
super().__init__(parent)
self.setWindowTitle(title)
self.file_path = file_path
self.editor = CodeEditor()
self.highlighter = JSONSyntaxHighlighter(self.editor.document())
self.initUI()
self.openFile()

def initUI(self):
layout = QVBoxLayout()
layout.addWidget(self.editor)
self.setLayout(layout)
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)

self.setMinimumSize(800, 600)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
self.setSizeGripEnabled(True)
self.show()

def saveFile(self):
with open(self.file_path, 'w') as f:
file_data = self.editor.toPlainText()
f.write(file_data)

def openFile(self):
with open(self.file_path, 'r') as f:
file_data = f.read()
self.editor.setPlainText(file_data)

def closeEvent(self, event=None):
if not self.editor.modified:
return
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setWindowIcon(self.parent().windowIcon())
msg.setWindowTitle("Save Changes?")
msg.setText("Do you want to save changes to this file?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
ret = msg.exec_()
if ret == QMessageBox.Yes:
self.saveFile()
if event:
event.accept()

def isModified(self):
return self.editor.modified


def main():
app = QApplication(sys.argv)
editor = JSONEditor(sys.argv[1])
editor.exec_()
sys.exit(app.exec_())


if __name__ == "__main__":
main()

0 comments on commit 4cae06b

Please sign in to comment.