| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812 |
- # ########################################################## ##
- # FlatCAM: 2D Post-processing for Manufacturing #
- # http://flatcam.org #
- # Author: Juan Pablo Caram (c) #
- # Date: 2/5/2014 #
- # MIT Licence #
- # ########################################################## ##
- # ########################################################## ##
- # File Modified (major mod): Marius Adrian Stanciu #
- # Date: 3/10/2019 #
- # ########################################################## ##
- from PyQt5 import QtGui, QtCore, QtWidgets
- from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
- from PyQt5.QtWidgets import QTextEdit, QCompleter, QAction
- from PyQt5.QtGui import QColor, QKeySequence, QPalette, QTextCursor
- from copy import copy
- import re
- import logging
- import html
- log = logging.getLogger('base')
- EDIT_SIZE_HINT = 70
- class RadioSet(QtWidgets.QWidget):
- activated_custom = QtCore.pyqtSignal(str)
- def __init__(self, choices, orientation='horizontal', parent=None, stretch=None):
- """
- The choices are specified as a list of dictionaries containing:
- * 'label': Shown in the UI
- * 'value': The value returned is selected
- :param choices: List of choices. See description.
- :param orientation: 'horizontal' (default) of 'vertical'.
- :param parent: Qt parent widget.
- :type choices: list
- """
- super(RadioSet, self).__init__(parent)
- self.choices = copy(choices)
- if orientation == 'horizontal':
- layout = QtWidgets.QHBoxLayout()
- else:
- layout = QtWidgets.QVBoxLayout()
- group = QtWidgets.QButtonGroup(self)
- for choice in self.choices:
- choice['radio'] = QtWidgets.QRadioButton(choice['label'])
- group.addButton(choice['radio'])
- layout.addWidget(choice['radio'], stretch=0)
- choice['radio'].toggled.connect(self.on_toggle)
- layout.setContentsMargins(0, 0, 0, 0)
- if stretch is False:
- pass
- else:
- layout.addStretch()
- self.setLayout(layout)
- self.group_toggle_fn = lambda: None
- def on_toggle(self):
- # log.debug("Radio toggled")
- radio = self.sender()
- if radio.isChecked():
- self.group_toggle_fn()
- ret_val = str(self.get_value())
- self.activated_custom.emit(ret_val)
- return
- def get_value(self):
- for choice in self.choices:
- if choice['radio'].isChecked():
- return choice['value']
- log.error("No button was toggled in RadioSet.")
- return None
- def set_value(self, val):
- for choice in self.choices:
- if choice['value'] == val:
- choice['radio'].setChecked(True)
- return
- log.error("Value given is not part of this RadioSet: %s" % str(val))
- # class RadioGroupChoice(QtWidgets.QWidget):
- # def __init__(self, label_1, label_2, to_check, hide_list, show_list, parent=None):
- # """
- # The choices are specified as a list of dictionaries containing:
- #
- # * 'label': Shown in the UI
- # * 'value': The value returned is selected
- #
- # :param choices: List of choices. See description.
- # :param orientation: 'horizontal' (default) of 'vertical'.
- # :param parent: Qt parent widget.
- # :type choices: list
- # """
- # super().__init__(parent)
- #
- # group = QtGui.QButtonGroup(self)
- #
- # self.lbl1 = label_1
- # self.lbl2 = label_2
- # self.hide_list = hide_list
- # self.show_list = show_list
- #
- # self.btn1 = QtGui.QRadioButton(str(label_1))
- # self.btn2 = QtGui.QRadioButton(str(label_2))
- # group.addButton(self.btn1)
- # group.addButton(self.btn2)
- #
- # if to_check == 1:
- # self.btn1.setChecked(True)
- # else:
- # self.btn2.setChecked(True)
- #
- # self.btn1.toggled.connect(lambda: self.btn_state(self.btn1))
- # self.btn2.toggled.connect(lambda: self.btn_state(self.btn2))
- #
- # def btn_state(self, btn):
- # if btn.text() == self.lbl1:
- # if btn.isChecked() is True:
- # self.show_widgets(self.show_list)
- # self.hide_widgets(self.hide_list)
- # else:
- # self.show_widgets(self.hide_list)
- # self.hide_widgets(self.show_list)
- #
- # def hide_widgets(self, lst):
- # for wgt in lst:
- # wgt.hide()
- #
- # def show_widgets(self, lst):
- # for wgt in lst:
- # wgt.show()
- class LengthEntry(QtWidgets.QLineEdit):
- def __init__(self, output_units='IN', parent=None):
- super(LengthEntry, self).__init__(parent)
- self.output_units = output_units
- self.format_re = re.compile(r"^([^\s]+)(?:\s([a-zA-Z]+))?$")
- # Unit conversion table OUTPUT-INPUT
- self.scales = {
- 'IN': {'IN': 1.0,
- 'MM': 1/25.4},
- 'MM': {'IN': 25.4,
- 'MM': 1.0}
- }
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(LengthEntry, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(LengthEntry, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def returnPressed(self, *args, **kwargs):
- val = self.get_value()
- if val is not None:
- self.set_text(str(val))
- else:
- log.warning("Could not interpret entry: %s" % self.get_text())
- def get_value(self):
- raw = str(self.text()).strip(' ')
- # match = self.format_re.search(raw)
- try:
- units = raw[-2:]
- units = self.scales[self.output_units][units.upper()]
- value = raw[:-2]
- return float(eval(value))* units
- except IndexError:
- value = raw
- return float(eval(value))
- except KeyError:
- value = raw
- return float(eval(value))
- except:
- log.warning("Could not parse value in entry: %s" % str(raw))
- return None
- def set_value(self, val):
- self.setText(str('%.4f' % val))
- def sizeHint(self):
- default_hint_size = super(LengthEntry, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FloatEntry(QtWidgets.QLineEdit):
- def __init__(self, parent=None):
- super(FloatEntry, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(FloatEntry, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit == True:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(FloatEntry, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def returnPressed(self, *args, **kwargs):
- val = self.get_value()
- if val is not None:
- self.set_text(str(val))
- else:
- log.warning("Could not interpret entry: %s" % self.text())
- def get_value(self):
- raw = str(self.text()).strip(' ')
- try:
- evaled = eval(raw)
- return float(evaled)
- except Exception as e:
- if raw is not '':
- log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
- return None
- def set_value(self, val):
- if val is not None:
- self.setText("%.4f" % float(val))
- else:
- self.setText("")
- def sizeHint(self):
- default_hint_size = super(FloatEntry, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FloatEntry2(QtWidgets.QLineEdit):
- def __init__(self, parent=None):
- super(FloatEntry2, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(FloatEntry2, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(FloatEntry2, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def get_value(self):
- raw = str(self.text()).strip(' ')
- try:
- evaled = eval(raw)
- return float(evaled)
- except Exception as e:
- if raw is not '':
- log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
- return None
- def set_value(self, val):
- self.setText("%.4f" % val)
- def sizeHint(self):
- default_hint_size = super(FloatEntry2, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class IntEntry(QtWidgets.QLineEdit):
- def __init__(self, parent=None, allow_empty=False, empty_val=None):
- super(IntEntry, self).__init__(parent)
- self.allow_empty = allow_empty
- self.empty_val = empty_val
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(IntEntry, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(IntEntry, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def get_value(self):
- if self.allow_empty:
- if str(self.text()) == "":
- return self.empty_val
- # make the text() first a float and then int because if text is a float type,
- # the int() can't convert directly a "text float" into a int type.
- ret_val = float(self.text())
- ret_val = int(ret_val)
- return ret_val
- def set_value(self, val):
- if val == self.empty_val and self.allow_empty:
- self.setText("")
- return
- self.setText(str(val))
- def sizeHint(self):
- default_hint_size = super(IntEntry, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCEntry(QtWidgets.QLineEdit):
- def __init__(self, parent=None):
- super(FCEntry, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(FCEntry, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(FCEntry, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def get_value(self):
- return str(self.text())
- def set_value(self, val):
- if type(val) is float:
- self.setText('%.4f' % val)
- else:
- self.setText(str(val))
- def sizeHint(self):
- default_hint_size = super(FCEntry, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCEntry2(FCEntry):
- def __init__(self, parent=None):
- super(FCEntry2, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def set_value(self, val, decimals=4):
- try:
- fval = float(val)
- except ValueError:
- return
- self.setText('%.*f' % (decimals, fval))
- class FCEntry3(FCEntry):
- def __init__(self, parent=None):
- super(FCEntry3, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def set_value(self, val, decimals=4):
- try:
- fval = float(val)
- except ValueError:
- return
- self.setText('%.*f' % (decimals, fval))
- def get_value(self):
- value = str(self.text()).strip(' ')
- try:
- return float(eval(value))
- except Exception as e:
- log.warning("Could not parse value in entry: %s" % str(e))
- return None
- class EvalEntry(QtWidgets.QLineEdit):
- def __init__(self, parent=None):
- super(EvalEntry, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(EvalEntry, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(EvalEntry, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def returnPressed(self, *args, **kwargs):
- val = self.get_value()
- if val is not None:
- self.setText(str(val))
- else:
- log.warning("Could not interpret entry: %s" % self.get_text())
- def get_value(self):
- raw = str(self.text()).strip(' ')
- evaled = 0.0
- try:
- evaled = eval(raw)
- except Exception as e:
- if raw is not '':
- log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
- return None
- return evaled
- def set_value(self, val):
- self.setText(str(val))
- def sizeHint(self):
- default_hint_size = super(EvalEntry, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class EvalEntry2(QtWidgets.QLineEdit):
- def __init__(self, parent=None):
- super(EvalEntry2, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, Parent=None):
- super(EvalEntry2, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(EvalEntry2, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.deselect()
- self.readyToEdit = True
- def get_value(self):
- raw = str(self.text()).strip(' ')
- evaled = 0.0
- try:
- evaled = eval(raw)
- except Exception as e:
- if raw is not '':
- log.error("Could not evaluate val: %s, error: %s" % (str(raw), str(e)))
- return None
- return evaled
- def set_value(self, val):
- self.setText(str(val))
- def sizeHint(self):
- default_hint_size = super(EvalEntry2, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCCheckBox(QtWidgets.QCheckBox):
- def __init__(self, label='', parent=None):
- super(FCCheckBox, self).__init__(str(label), parent)
- def get_value(self):
- return self.isChecked()
- def set_value(self, val):
- self.setChecked(val)
- def toggle(self):
- self.set_value(not self.get_value())
- class FCTextArea(QtWidgets.QPlainTextEdit):
- def __init__(self, parent=None):
- super(FCTextArea, self).__init__(parent)
- def set_value(self, val):
- self.setPlainText(val)
- def get_value(self):
- return str(self.toPlainText())
- def sizeHint(self):
- default_hint_size = super(FCTextArea, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCTextAreaRich(QtWidgets.QTextEdit):
- def __init__(self, parent=None):
- super(FCTextAreaRich, self).__init__(parent)
- def set_value(self, val):
- self.setText(val)
- def get_value(self):
- return str(self.toPlainText())
- def sizeHint(self):
- default_hint_size = super(FCTextAreaRich, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCTextAreaExtended(QtWidgets.QTextEdit):
- def __init__(self, parent=None):
- super(FCTextAreaExtended, self).__init__(parent)
- self.completer = MyCompleter()
- self.model = QtCore.QStringListModel()
- self.completer.setModel(self.model)
- self.set_model_data(keyword_list=[])
- self.completer.insertText.connect(self.insertCompletion)
- self.completer_enable = False
- def set_model_data(self, keyword_list):
- self.model.setStringList(keyword_list)
- def insertCompletion(self, completion):
- tc = self.textCursor()
- extra = (len(completion) - len(self.completer.completionPrefix()))
- # don't insert if the word is finished but add a space instead
- if extra == 0:
- tc.insertText(' ')
- self.completer.popup().hide()
- return
- tc.movePosition(QTextCursor.Left)
- tc.movePosition(QTextCursor.EndOfWord)
- tc.insertText(completion[-extra:])
- # add a space after inserting the word
- tc.insertText(' ')
- self.setTextCursor(tc)
- self.completer.popup().hide()
- def focusInEvent(self, event):
- if self.completer:
- self.completer.setWidget(self)
- QTextEdit.focusInEvent(self, event)
- def set_value(self, val):
- self.setText(val)
- def get_value(self):
- self.toPlainText()
- def insertFromMimeData(self, data):
- """
- Reimplemented such that when SHIFT is pressed and doing click Paste in the contextual menu, the '\' symbol
- is replaced with the '/' symbol. That's because of the difference in path separators in Windows and TCL
- :param data:
- :return:
- """
- modifier = QtWidgets.QApplication.keyboardModifiers()
- if modifier == Qt.ShiftModifier:
- text = data.text()
- text = text.replace('\\', '/')
- self.insertPlainText(text)
- else:
- self.insertPlainText(data.text())
- def keyPressEvent(self, event):
- """
- Reimplemented so the CTRL + SHIFT + V shortcut key combo will paste the text but replacing '\' with '/'
- :param event:
- :return:
- """
- key = event.key()
- modifier = QtWidgets.QApplication.keyboardModifiers()
- if modifier & Qt.ControlModifier and modifier & Qt.ShiftModifier:
- if key == QtCore.Qt.Key_V:
- clipboard = QtWidgets.QApplication.clipboard()
- clip_text = clipboard.text()
- clip_text = clip_text.replace('\\', '/')
- self.insertPlainText(clip_text)
- if modifier & Qt.ControlModifier and key == Qt.Key_Slash:
- self.comment()
- tc = self.textCursor()
- if (key == Qt.Key_Tab or key == Qt.Key_Enter or key == Qt.Key_Return) and self.completer.popup().isVisible():
- self.completer.insertText.emit(self.completer.getSelected())
- self.completer.setCompletionMode(QCompleter.PopupCompletion)
- return
- elif key == Qt.Key_BraceLeft:
- tc.insertText('{}')
- self.moveCursor(QtGui.QTextCursor.Left)
- elif key == Qt.Key_BracketLeft:
- tc.insertText('[]')
- self.moveCursor(QtGui.QTextCursor.Left)
- elif key == Qt.Key_ParenLeft:
- tc.insertText('()')
- self.moveCursor(QtGui.QTextCursor.Left)
- elif key == Qt.Key_BraceRight:
- tc.select(QtGui.QTextCursor.WordUnderCursor)
- if tc.selectedText() == '}':
- tc.movePosition(QTextCursor.Right)
- self.setTextCursor(tc)
- else:
- tc.clearSelection()
- self.textCursor().insertText('}')
- elif key == Qt.Key_BracketRight:
- tc.select(QtGui.QTextCursor.WordUnderCursor)
- if tc.selectedText() == ']':
- tc.movePosition(QTextCursor.Right)
- self.setTextCursor(tc)
- else:
- tc.clearSelection()
- self.textCursor().insertText(']')
- elif key == Qt.Key_ParenRight:
- tc.select(QtGui.QTextCursor.WordUnderCursor)
- if tc.selectedText() == ')':
- tc.movePosition(QTextCursor.Right)
- self.setTextCursor(tc)
- else:
- tc.clearSelection()
- self.textCursor().insertText(')')
- else:
- super(FCTextAreaExtended, self).keyPressEvent(event)
- if self.completer_enable:
- tc.select(QTextCursor.WordUnderCursor)
- cr = self.cursorRect()
- if len(tc.selectedText()) > 0:
- self.completer.setCompletionPrefix(tc.selectedText())
- popup = self.completer.popup()
- popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
- cr.setWidth(self.completer.popup().sizeHintForColumn(0)
- + self.completer.popup().verticalScrollBar().sizeHint().width())
- self.completer.complete(cr)
- else:
- self.completer.popup().hide()
- def comment(self):
- """
- Got it from here:
- https://stackoverflow.com/questions/49898820/how-to-get-text-next-to-cursor-in-qtextedit-in-pyqt4
- :return:
- """
- pos = self.textCursor().position()
- self.moveCursor(QtGui.QTextCursor.StartOfLine)
- line_text = self.textCursor().block().text()
- if self.textCursor().block().text().startswith(" "):
- # skip the white space
- self.moveCursor(QtGui.QTextCursor.NextWord)
- self.moveCursor(QtGui.QTextCursor.NextCharacter,QtGui.QTextCursor.KeepAnchor)
- character = self.textCursor().selectedText()
- if character == "#":
- # delete #
- self.textCursor().deletePreviousChar()
- # delete white space
- self.moveCursor(QtGui.QTextCursor.NextWord,QtGui.QTextCursor.KeepAnchor)
- self.textCursor().removeSelectedText()
- else:
- self.moveCursor(QtGui.QTextCursor.PreviousCharacter,QtGui.QTextCursor.KeepAnchor)
- self.textCursor().insertText("# ")
- cursor = QtGui.QTextCursor(self.textCursor())
- cursor.setPosition(pos)
- self.setTextCursor(cursor)
- class FCComboBox(QtWidgets.QComboBox):
- def __init__(self, parent=None, callback=None):
- super(FCComboBox, self).__init__(parent)
- self.setFocusPolicy(QtCore.Qt.StrongFocus)
- self.view = self.view()
- self.view.viewport().installEventFilter(self)
- self.view.setContextMenuPolicy(Qt.CustomContextMenu)
- # the callback() will be called on customcontextmenu event and will be be passed 2 parameters:
- # pos = mouse right click click position
- # self = is the combobox object itself
- if callback:
- self.view.customContextMenuRequested.connect(lambda pos: callback(pos, self))
- def eventFilter(self, obj, event):
- if event.type() == QtCore.QEvent.MouseButtonRelease:
- if event.button() == Qt.RightButton:
- return True
- return False
- def wheelEvent(self, *args, **kwargs):
- pass
- def get_value(self):
- return str(self.currentText())
- def set_value(self, val):
- self.setCurrentIndex(self.findText(str(val)))
- class FCInputDialog(QtWidgets.QInputDialog):
- def __init__(self, parent=None, ok=False, val=None, title=None, text=None, min=None, max=None, decimals=None,
- init_val=None):
- super(FCInputDialog, self).__init__(parent)
- self.allow_empty = ok
- self.empty_val = val
- self.val = 0.0
- self.ok = ''
- self.init_value = init_val if init_val else 0.0
- if title is None:
- self.title = 'title'
- else:
- self.title = title
- if text is None:
- self.text = 'text'
- else:
- self.text = text
- if min is None:
- self.min = 0
- else:
- self.min = min
- if max is None:
- self.max = 0
- else:
- self.max = max
- if decimals is None:
- self.decimals = 6
- else:
- self.decimals = decimals
- def get_value(self):
- self.val, self.ok = self.getDouble(self, self.title, self.text, min=self.min,
- max=self.max, decimals=self.decimals, value=self.init_value)
- return [self.val, self.ok]
- # "Transform", "Enter the Angle value:"
- def set_value(self, val):
- pass
- class FCButton(QtWidgets.QPushButton):
- def __init__(self, parent=None):
- super(FCButton, self).__init__(parent)
- def get_value(self):
- return self.isChecked()
- def set_value(self, val):
- self.setText(str(val))
- class FCMenu(QtWidgets.QMenu):
- def __init__(self):
- super().__init__()
- self.mouse_is_panning = False
- def popup(self, pos, action=None):
- self.mouse_is_panning = False
- super().popup(pos)
- class FCTab(QtWidgets.QTabWidget):
- def __init__(self, parent=None):
- super(FCTab, self).__init__(parent)
- self.setTabsClosable(True)
- self.tabCloseRequested.connect(self.closeTab)
- def deleteTab(self, currentIndex):
- widget = self.widget(currentIndex)
- if widget is not None:
- widget.deleteLater()
- self.removeTab(currentIndex)
- def closeTab(self, currentIndex):
- self.removeTab(currentIndex)
- def protectTab(self, currentIndex):
- self.tabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
- class FCDetachableTab(QtWidgets.QTabWidget):
- """
- From here:
- https://stackoverflow.com/questions/47267195/in-pyqt4-is-it-possible-to-detach-tabs-from-a-qtabwidget
- """
- def __init__(self, protect=None, protect_by_name=None, parent=None):
- super().__init__()
- self.tabBar = self.FCTabBar(self)
- self.tabBar.onDetachTabSignal.connect(self.detachTab)
- self.tabBar.onMoveTabSignal.connect(self.moveTab)
- self.tabBar.detachedTabDropSignal.connect(self.detachedTabDrop)
- self.setTabBar(self.tabBar)
- # Used to keep a reference to detached tabs since their QMainWindow
- # does not have a parent
- self.detachedTabs = {}
- # a way to make sure that tabs can't be closed after they attach to the parent tab
- self.protect_tab = True if protect is not None and protect is True else False
- self.protect_by_name = protect_by_name if isinstance(protect_by_name, list) else None
- # Close all detached tabs if the application is closed explicitly
- QtWidgets.qApp.aboutToQuit.connect(self.closeDetachedTabs) # @UndefinedVariable
- # used by the property self.useOldIndex(param)
- self.use_old_index = None
- self.old_index = None
- self.setTabsClosable(True)
- self.tabCloseRequested.connect(self.closeTab)
- def useOldIndex(self, param):
- if param:
- self.use_old_index = True
- else:
- self.use_old_index = False
- def deleteTab(self, currentIndex):
- widget = self.widget(currentIndex)
- if widget is not None:
- widget.deleteLater()
- self.removeTab(currentIndex)
- def closeTab(self, currentIndex):
- """
- Slot connected to the tabCloseRequested signal
- :param currentIndex:
- :return:
- """
- self.removeTab(currentIndex)
- def protectTab(self, currentIndex):
- # self.FCTabBar().setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
- self.tabBar.setTabButton(currentIndex, QtWidgets.QTabBar.RightSide, None)
- def setMovable(self, movable):
- """
- The default movable functionality of QTabWidget must remain disabled
- so as not to conflict with the added features
- :param movable:
- :return:
- """
- pass
- @pyqtSlot(int, int)
- def moveTab(self, fromIndex, toIndex):
- """
- Move a tab from one position (index) to another
- :param fromIndex: the original index location of the tab
- :param toIndex: the new index location of the tab
- :return:
- """
- widget = self.widget(fromIndex)
- icon = self.tabIcon(fromIndex)
- text = self.tabText(fromIndex)
- self.removeTab(fromIndex)
- self.insertTab(toIndex, widget, icon, text)
- self.setCurrentIndex(toIndex)
- @pyqtSlot(int, QtCore.QPoint)
- def detachTab(self, index, point):
- """
- Detach the tab by removing it's contents and placing them in
- a DetachedTab window
- :param index: the index location of the tab to be detached
- :param point: the screen position for creating the new DetachedTab window
- :return:
- """
- self.old_index = index
- # Get the tab content and add name FlatCAM to the tab so we know on which app is this tab linked
- name = "FlatCAM " + self.tabText(index)
- icon = self.tabIcon(index)
- if icon.isNull():
- icon = self.window().windowIcon()
- contentWidget = self.widget(index)
- try:
- contentWidgetRect = contentWidget.frameGeometry()
- except AttributeError:
- return
- # Create a new detached tab window
- detachedTab = self.FCDetachedTab(name, contentWidget)
- detachedTab.setWindowModality(QtCore.Qt.NonModal)
- detachedTab.setWindowIcon(icon)
- detachedTab.setGeometry(contentWidgetRect)
- detachedTab.onCloseSignal.connect(self.attachTab)
- detachedTab.onDropSignal.connect(self.tabBar.detachedTabDrop)
- detachedTab.move(point)
- detachedTab.show()
- # Create a reference to maintain access to the detached tab
- self.detachedTabs[name] = detachedTab
- def attachTab(self, contentWidget, name, icon, insertAt=None):
- """
- Re-attach the tab by removing the content from the DetachedTab window,
- closing it, and placing the content back into the DetachableTabWidget
- :param contentWidget: the content widget from the DetachedTab window
- :param name: the name of the detached tab
- :param icon: the window icon for the detached tab
- :param insertAt: insert the re-attached tab at the given index
- :return:
- """
- # Make the content widget a child of this widget
- contentWidget.setParent(self)
- # Remove the reference
- del self.detachedTabs[name]
- # make sure that we strip the 'FlatCAM' part of the detached name otherwise the tab name will be too long
- name = name.partition(' ')[2]
- # helps in restoring the tab to the same index that it was before was detached
- insert_index = self.old_index if self.use_old_index is True else insertAt
- # Create an image from the given icon (for comparison)
- if not icon.isNull():
- try:
- tabIconPixmap = icon.pixmap(icon.availableSizes()[0])
- tabIconImage = tabIconPixmap.toImage()
- except IndexError:
- tabIconImage = None
- else:
- tabIconImage = None
- # Create an image of the main window icon (for comparison)
- if not icon.isNull():
- try:
- windowIconPixmap = self.window().windowIcon().pixmap(icon.availableSizes()[0])
- windowIconImage = windowIconPixmap.toImage()
- except IndexError:
- windowIconImage = None
- else:
- windowIconImage = None
- # Determine if the given image and the main window icon are the same.
- # If they are, then do not add the icon to the tab
- if tabIconImage == windowIconImage:
- if insert_index is None:
- index = self.addTab(contentWidget, name)
- else:
- index = self.insertTab(insert_index, contentWidget, name)
- else:
- if insert_index is None:
- index = self.addTab(contentWidget, icon, name)
- else:
- index = self.insertTab(insert_index, contentWidget, icon, name)
- # on reattaching the tab if protect is true then the closure button is not added
- if self.protect_tab is True:
- self.protectTab(index)
- # on reattaching the tab disable the closure button for the tabs with the name in the self.protect_by_name list
- if self.protect_by_name is not None:
- for tab_name in self.protect_by_name:
- for index in range(self.count()):
- if str(tab_name) == str(self.tabText(index)):
- self.protectTab(index)
- # Make this tab the current tab
- if index > -1:
- self.setCurrentIndex(insert_index) if self.use_old_index else self.setCurrentIndex(index)
- def removeTabByName(self, name):
- """
- Remove the tab with the given name, even if it is detached
- :param name: the name of the tab to be removed
- :return:
- """
- # Remove the tab if it is attached
- attached = False
- for index in range(self.count()):
- if str(name) == str(self.tabText(index)):
- self.removeTab(index)
- attached = True
- break
- # If the tab is not attached, close it's window and
- # remove the reference to it
- if not attached:
- for key in self.detachedTabs:
- if str(name) == str(key):
- self.detachedTabs[key].onCloseSignal.disconnect()
- self.detachedTabs[key].close()
- del self.detachedTabs[key]
- break
- @QtCore.pyqtSlot(str, int, QtCore.QPoint)
- def detachedTabDrop(self, name, index, dropPos):
- """
- Handle dropping of a detached tab inside the DetachableTabWidget
- :param name: the name of the detached tab
- :param index: the index of an existing tab (if the tab bar
- # determined that the drop occurred on an
- # existing tab)
- :param dropPos: the mouse cursor position when the drop occurred
- :return:
- """
- # If the drop occurred on an existing tab, insert the detached
- # tab at the existing tab's location
- if index > -1:
- # Create references to the detached tab's content and icon
- contentWidget = self.detachedTabs[name].contentWidget
- icon = self.detachedTabs[name].windowIcon()
- # Disconnect the detached tab's onCloseSignal so that it
- # does not try to re-attach automatically
- self.detachedTabs[name].onCloseSignal.disconnect()
- # Close the detached
- self.detachedTabs[name].close()
- # Re-attach the tab at the given index
- self.attachTab(contentWidget, name, icon, index)
- # If the drop did not occur on an existing tab, determine if the drop
- # occurred in the tab bar area (the area to the side of the QTabBar)
- else:
- # Find the drop position relative to the DetachableTabWidget
- tabDropPos = self.mapFromGlobal(dropPos)
- # If the drop position is inside the DetachableTabWidget...
- if self.rect().contains(tabDropPos):
- # If the drop position is inside the tab bar area (the
- # area to the side of the QTabBar) or there are not tabs
- # currently attached...
- if tabDropPos.y() < self.tabBar.height() or self.count() == 0:
- # Close the detached tab and allow it to re-attach
- # automatically
- self.detachedTabs[name].close()
- def closeDetachedTabs(self):
- """
- Close all tabs that are currently detached.
- :return:
- """
- listOfDetachedTabs = []
- for key in self.detachedTabs:
- listOfDetachedTabs.append(self.detachedTabs[key])
- for detachedTab in listOfDetachedTabs:
- detachedTab.close()
- class FCDetachedTab(QtWidgets.QMainWindow):
- """
- When a tab is detached, the contents are placed into this QMainWindow. The tab
- can be re-attached by closing the dialog or by dragging the window into the tab bar
- """
- onCloseSignal = pyqtSignal(QtWidgets.QWidget, str, QtGui.QIcon)
- onDropSignal = pyqtSignal(str, QtCore.QPoint)
- def __init__(self, name, contentWidget):
- QtWidgets.QMainWindow.__init__(self, None)
- self.setObjectName(name)
- self.setWindowTitle(name)
- self.contentWidget = contentWidget
- self.setCentralWidget(self.contentWidget)
- self.contentWidget.show()
- self.windowDropFilter = self.WindowDropFilter()
- self.installEventFilter(self.windowDropFilter)
- self.windowDropFilter.onDropSignal.connect(self.windowDropSlot)
- @QtCore.pyqtSlot(QtCore.QPoint)
- def windowDropSlot(self, dropPos):
- """
- Handle a window drop event
- :param dropPos: the mouse cursor position of the drop
- :return:
- """
- self.onDropSignal.emit(self.objectName(), dropPos)
- def closeEvent(self, event):
- """
- If the window is closed, emit the onCloseSignal and give the
- content widget back to the DetachableTabWidget
- :param event: a close event
- :return:
- """
- self.onCloseSignal.emit(self.contentWidget, self.objectName(), self.windowIcon())
- class WindowDropFilter(QtCore.QObject):
- """
- An event filter class to detect a QMainWindow drop event
- """
- onDropSignal = pyqtSignal(QtCore.QPoint)
- def __init__(self):
- QtCore.QObject.__init__(self)
- self.lastEvent = None
- def eventFilter(self, obj, event):
- """
- Detect a QMainWindow drop event by looking for a NonClientAreaMouseMove (173)
- event that immediately follows a Move event
- :param obj: the object that generated the event
- :param event: the current event
- :return:
- """
- # If a NonClientAreaMouseMove (173) event immediately follows a Move event...
- if self.lastEvent == QtCore.QEvent.Move and event.type() == 173:
- # Determine the position of the mouse cursor and emit it with the
- # onDropSignal
- mouseCursor = QtGui.QCursor()
- dropPos = mouseCursor.pos()
- self.onDropSignal.emit(dropPos)
- self.lastEvent = event.type()
- return True
- else:
- self.lastEvent = event.type()
- return False
- class FCTabBar(QtWidgets.QTabBar):
- onDetachTabSignal = pyqtSignal(int, QtCore.QPoint)
- onMoveTabSignal = pyqtSignal(int, int)
- detachedTabDropSignal = pyqtSignal(str, int, QtCore.QPoint)
- def __init__(self, parent=None):
- QtWidgets.QTabBar.__init__(self, parent)
- self.setAcceptDrops(True)
- self.setElideMode(QtCore.Qt.ElideRight)
- self.setSelectionBehaviorOnRemove(QtWidgets.QTabBar.SelectLeftTab)
- self.dragStartPos = QtCore.QPoint()
- self.dragDropedPos = QtCore.QPoint()
- self.mouseCursor = QtGui.QCursor()
- self.dragInitiated = False
- def mouseDoubleClickEvent(self, event):
- """
- Send the onDetachTabSignal when a tab is double clicked
- :param event: a mouse double click event
- :return:
- """
- event.accept()
- self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
- def mousePressEvent(self, event):
- """
- Set the starting position for a drag event when the mouse button is pressed
- :param event: a mouse press event
- :return:
- """
- if event.button() == QtCore.Qt.LeftButton:
- self.dragStartPos = event.pos()
- self.dragDropedPos.setX(0)
- self.dragDropedPos.setY(0)
- self.dragInitiated = False
- QtWidgets.QTabBar.mousePressEvent(self, event)
- def mouseMoveEvent(self, event):
- """
- Determine if the current movement is a drag. If it is, convert it into a QDrag. If the
- drag ends inside the tab bar, emit an onMoveTabSignal. If the drag ends outside the tab
- bar, emit an onDetachTabSignal.
- :param event: a mouse move event
- :return:
- """
- # Determine if the current movement is detected as a drag
- if not self.dragStartPos.isNull() and \
- ((event.pos() - self.dragStartPos).manhattanLength() < QtWidgets.QApplication.startDragDistance()):
- self.dragInitiated = True
- # If the current movement is a drag initiated by the left button
- if (((event.buttons() & QtCore.Qt.LeftButton)) and self.dragInitiated):
- # Stop the move event
- finishMoveEvent = QtGui.QMouseEvent(
- QtCore.QEvent.MouseMove, event.pos(), QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier
- )
- QtWidgets.QTabBar.mouseMoveEvent(self, finishMoveEvent)
- # Convert the move event into a drag
- drag = QtGui.QDrag(self)
- mimeData = QtCore.QMimeData()
- # mimeData.setData('action', 'application/tab-detach')
- drag.setMimeData(mimeData)
- # screen = QScreen(self.parentWidget().currentWidget().winId())
- # Create the appearance of dragging the tab content
- try:
- pixmap = self.parent().widget(self.tabAt(self.dragStartPos)).grab()
- except Exception as e:
- log.debug("GUIElements.FCDetachable. FCTabBar.mouseMoveEvent() --> %s" % str(e))
- return
- targetPixmap = QtGui.QPixmap(pixmap.size())
- targetPixmap.fill(QtCore.Qt.transparent)
- painter = QtGui.QPainter(targetPixmap)
- painter.setOpacity(0.85)
- painter.drawPixmap(0, 0, pixmap)
- painter.end()
- drag.setPixmap(targetPixmap)
- # Initiate the drag
- dropAction = drag.exec_(QtCore.Qt.MoveAction | QtCore.Qt.CopyAction)
- # For Linux: Here, drag.exec_() will not return MoveAction on Linux. So it
- # must be set manually
- if self.dragDropedPos.x() != 0 and self.dragDropedPos.y() != 0:
- dropAction = QtCore.Qt.MoveAction
- # If the drag completed outside of the tab bar, detach the tab and move
- # the content to the current cursor position
- if dropAction == QtCore.Qt.IgnoreAction:
- event.accept()
- self.onDetachTabSignal.emit(self.tabAt(self.dragStartPos), self.mouseCursor.pos())
- # Else if the drag completed inside the tab bar, move the selected tab to the new position
- elif dropAction == QtCore.Qt.MoveAction:
- if not self.dragDropedPos.isNull():
- event.accept()
- self.onMoveTabSignal.emit(self.tabAt(self.dragStartPos), self.tabAt(self.dragDropedPos))
- else:
- QtWidgets.QTabBar.mouseMoveEvent(self, event)
- def dragEnterEvent(self, event):
- """
- Determine if the drag has entered a tab position from another tab position
- :param event: a drag enter event
- :return:
- """
- mimeData = event.mimeData()
- # formats = mcd imeData.formats()
- # if formats.contains('action') and mimeData.data('action') == 'application/tab-detach':
- # event.acceptProposedAction()
- QtWidgets.QTabBar.dragMoveEvent(self, event)
- def dropEvent(self, event):
- """
- Get the position of the end of the drag
- :param event: a drop event
- :return:
- """
- self.dragDropedPos = event.pos()
- QtWidgets.QTabBar.dropEvent(self, event)
- def detachedTabDrop(self, name, dropPos):
- """
- Determine if the detached tab drop event occurred on an existing tab,
- then send the event to the DetachableTabWidget
- :param name:
- :param dropPos:
- :return:
- """
- tabDropPos = self.mapFromGlobal(dropPos)
- index = self.tabAt(tabDropPos)
- self.detachedTabDropSignal.emit(name, index, dropPos)
- class FCDetachableTab2(FCDetachableTab):
- tab_closed_signal = pyqtSignal()
- def __init__(self, protect=None, protect_by_name=None, parent=None):
- super(FCDetachableTab2, self).__init__(protect=protect, protect_by_name=protect_by_name, parent=parent)
- def closeTab(self, currentIndex):
- """
- Slot connected to the tabCloseRequested signal
- :param currentIndex:
- :return:
- """
- idx = self.currentIndex()
- # emit the signal only if the name is the one we want; the name should be a parameter somehow
- if self.tabText(idx) == _("Preferences"):
- self.tab_closed_signal.emit()
- self.removeTab(currentIndex)
- class VerticalScrollArea(QtWidgets.QScrollArea):
- """
- This widget extends QtGui.QScrollArea to make a vertical-only
- scroll area that also expands horizontally to accomodate
- its contents.
- """
- def __init__(self, parent=None):
- QtWidgets.QScrollArea.__init__(self, parent=parent)
- self.setWidgetResizable(True)
- self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
- self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
- def eventFilter(self, source, event):
- """
- The event filter gets automatically installed when setWidget()
- is called.
- :param source:
- :param event:
- :return:
- """
- if event.type() == QtCore.QEvent.Resize and source == self.widget():
- # log.debug("VerticalScrollArea: Widget resized:")
- # log.debug(" minimumSizeHint().width() = %d" % self.widget().minimumSizeHint().width())
- # log.debug(" verticalScrollBar().width() = %d" % self.verticalScrollBar().width())
- self.setMinimumWidth(self.widget().sizeHint().width() +
- self.verticalScrollBar().sizeHint().width())
- # if self.verticalScrollBar().isVisible():
- # log.debug(" Scroll bar visible")
- # self.setMinimumWidth(self.widget().minimumSizeHint().width() +
- # self.verticalScrollBar().width())
- # else:
- # log.debug(" Scroll bar hidden")
- # self.setMinimumWidth(self.widget().minimumSizeHint().width())
- return QtWidgets.QWidget.eventFilter(self, source, event)
- class OptionalInputSection:
- def __init__(self, cb, optinputs, logic=True):
- """
- Associates the a checkbox with a set of inputs.
- :param cb: Checkbox that enables the optional inputs.
- :param optinputs: List of widgets that are optional.
- :param logic: When True the logic is normal, when False the logic is in reverse
- It means that for logic=True, when the checkbox is checked the widgets are Enabled, and
- for logic=False, when the checkbox is checked the widgets are Disabled
- :return:
- """
- assert isinstance(cb, FCCheckBox), \
- "Expected an FCCheckBox, got %s" % type(cb)
- self.cb = cb
- self.optinputs = optinputs
- self.logic = logic
- self.on_cb_change()
- self.cb.stateChanged.connect(self.on_cb_change)
- def on_cb_change(self):
- if self.cb.checkState():
- for widget in self.optinputs:
- if self.logic is True:
- widget.setEnabled(True)
- else:
- widget.setEnabled(False)
- else:
- for widget in self.optinputs:
- if self.logic is True:
- widget.setEnabled(False)
- else:
- widget.setEnabled(True)
- class FCTable(QtWidgets.QTableWidget):
- def __init__(self, parent=None):
- super(FCTable, self).__init__(parent)
- def sizeHint(self):
- default_hint_size = super(FCTable, self).sizeHint()
- return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- def getHeight(self):
- height = self.horizontalHeader().height()
- for i in range(self.rowCount()):
- height += self.rowHeight(i)
- return height
- def getWidth(self):
- width = self.verticalHeader().width()
- for i in range(self.columnCount()):
- width += self.columnWidth(i)
- return width
- # color is in format QtGui.Qcolor(r, g, b, alpha) with or without alpfa
- def setColortoRow(self, rowIndex, color):
- for j in range(self.columnCount()):
- self.item(rowIndex, j).setBackground(color)
- # if user is clicking an blank area inside the QTableWidget it will deselect currently selected rows
- def mousePressEvent(self, event):
- if self.itemAt(event.pos()) is None:
- self.clearSelection()
- else:
- QtWidgets.QTableWidget.mousePressEvent(self, event)
- def setupContextMenu(self):
- self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
- def addContextMenu(self, entry, call_function, icon=None):
- action_name = str(entry)
- action = QtWidgets.QAction(self)
- action.setText(action_name)
- if icon:
- assert isinstance(icon, QtGui.QIcon), \
- "Expected the argument to be QtGui.QIcon. Instead it is %s" % type(icon)
- action.setIcon(icon)
- self.addAction(action)
- action.triggered.connect(call_function)
- class SpinBoxDelegate(QtWidgets.QItemDelegate):
- def __init__(self, units):
- super(SpinBoxDelegate, self).__init__()
- self.units = units
- self.current_value = None
- def createEditor(self, parent, option, index):
- editor = QtWidgets.QDoubleSpinBox(parent)
- editor.setMinimum(-999.9999)
- editor.setMaximum(999.9999)
- if self.units == 'MM':
- editor.setDecimals(2)
- else:
- editor.setDecimals(3)
- return editor
- def setEditorData(self, spinBox, index):
- try:
- value = float(index.model().data(index, Qt.EditRole))
- except ValueError:
- value = self.current_value
- # return
- spinBox.setValue(value)
- def setModelData(self, spinBox, model, index):
- spinBox.interpretText()
- value = spinBox.value()
- self.current_value = value
- model.setData(index, value, Qt.EditRole)
- def updateEditorGeometry(self, editor, option, index):
- editor.setGeometry(option.rect)
- def setDecimals(self, spinbox, digits):
- spinbox.setDecimals(digits)
- class FCSpinner(QtWidgets.QSpinBox):
- def __init__(self, parent=None):
- super(FCSpinner, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, parent=None):
- super(FCSpinner, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.lineEdit().selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(FCSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.lineEdit().deselect()
- self.readyToEdit = True
- def get_value(self):
- return str(self.value())
- def set_value(self, val):
- try:
- k = int(val)
- except Exception as e:
- log.debug(str(e))
- return
- self.setValue(k)
- def set_range(self, min_val, max_val):
- self.setRange(min_val, max_val)
- # def sizeHint(self):
- # default_hint_size = super(FCSpinner, self).sizeHint()
- # return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
- class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
- def __init__(self, parent=None):
- super(FCDoubleSpinner, self).__init__(parent)
- self.readyToEdit = True
- self.editingFinished.connect(self.on_edit_finished)
- def on_edit_finished(self):
- self.clearFocus()
- def mousePressEvent(self, e, parent=None):
- super(FCDoubleSpinner, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.lineEdit().selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(FCDoubleSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.lineEdit().deselect()
- self.readyToEdit = True
- def get_value(self):
- return str(self.value())
- def set_value(self, val):
- try:
- k = int(val)
- except Exception as e:
- log.debug(str(e))
- return
- self.setValue(k)
- def set_precision(self, val):
- self.setDecimals(val)
- def set_range(self, min_val, max_val):
- self.setRange(min_val, max_val)
- class Dialog_box(QtWidgets.QWidget):
- def __init__(self, title=None, label=None, icon=None):
- """
- :param title: string with the window title
- :param label: string with the message inside the dialog box
- """
- super(Dialog_box, self).__init__()
- self.location = (0, 0)
- self.ok = False
- dialog_box = QtWidgets.QInputDialog()
- dialog_box.setFixedWidth(290)
- self.setWindowIcon(icon)
- self.location, self.ok = dialog_box.getText(self, title, label, text="0, 0")
- self.readyToEdit = True
- def mousePressEvent(self, e, parent=None):
- super(Dialog_box, self).mousePressEvent(e) # required to deselect on 2e click
- if self.readyToEdit:
- self.lineEdit().selectAll()
- self.readyToEdit = False
- def focusOutEvent(self, e):
- super(Dialog_box, self).focusOutEvent(e) # required to remove cursor on focusOut
- self.lineEdit().deselect()
- self.readyToEdit = True
- class _BrowserTextEdit(QTextEdit):
- def __init__(self, version):
- QTextEdit.__init__(self)
- self.menu = None
- self.version = version
- def contextMenuEvent(self, event):
- self.menu = self.createStandardContextMenu(event.pos())
- clear_action = QAction("Clear", self)
- clear_action.setShortcut(QKeySequence(Qt.Key_Delete)) # it's not working, the shortcut
- self.menu.addAction(clear_action)
- clear_action.triggered.connect(self.clear)
- self.menu.exec_(event.globalPos())
- def clear(self):
- QTextEdit.clear(self)
- text = "FlatCAM %s (c)2014-2019 Juan Pablo Caram (Type help to get started)\n\n" % self.version
- text = html.escape(text)
- text = text.replace('\n', '<br/>')
- self.moveCursor(QTextCursor.End)
- self.insertHtml(text)
- class _ExpandableTextEdit(QTextEdit):
- """
- Class implements edit line, which expands themselves automatically
- """
- historyNext = pyqtSignal()
- historyPrev = pyqtSignal()
- def __init__(self, termwidget, *args):
- QTextEdit.__init__(self, *args)
- self.setStyleSheet("font: 9pt \"Courier\";")
- self._fittedHeight = 1
- self.textChanged.connect(self._fit_to_document)
- self._fit_to_document()
- self._termWidget = termwidget
- self.completer = MyCompleter()
- self.model = QtCore.QStringListModel()
- self.completer.setModel(self.model)
- self.set_model_data(keyword_list=[])
- self.completer.insertText.connect(self.insertCompletion)
- def set_model_data(self, keyword_list):
- self.model.setStringList(keyword_list)
- def insertCompletion(self, completion):
- tc = self.textCursor()
- extra = (len(completion) - len(self.completer.completionPrefix()))
- # don't insert if the word is finished but add a space instead
- if extra == 0:
- tc.insertText(' ')
- self.completer.popup().hide()
- return
- tc.movePosition(QTextCursor.Left)
- tc.movePosition(QTextCursor.EndOfWord)
- tc.insertText(completion[-extra:])
- # add a space after inserting the word
- tc.insertText(' ')
- self.setTextCursor(tc)
- self.completer.popup().hide()
- def focusInEvent(self, event):
- if self.completer:
- self.completer.setWidget(self)
- QTextEdit.focusInEvent(self, event)
- def keyPressEvent(self, event):
- """
- Catch keyboard events. Process Enter, Up, Down
- """
- key = event.key()
- if (key == Qt.Key_Tab or key == Qt.Key_Return or key == Qt.Key_Enter) and self.completer.popup().isVisible():
- self.completer.insertText.emit(self.completer.getSelected())
- self.completer.setCompletionMode(QCompleter.PopupCompletion)
- return
- if event.matches(QKeySequence.InsertParagraphSeparator):
- text = self.toPlainText()
- if self._termWidget.is_command_complete(text):
- self._termWidget.exec_current_command()
- return
- elif event.matches(QKeySequence.MoveToNextLine):
- text = self.toPlainText()
- cursor_pos = self.textCursor().position()
- textBeforeEnd = text[cursor_pos:]
- if len(textBeforeEnd.split('\n')) <= 1:
- self.historyNext.emit()
- return
- elif event.matches(QKeySequence.MoveToPreviousLine):
- text = self.toPlainText()
- cursor_pos = self.textCursor().position()
- text_before_start = text[:cursor_pos]
- # lineCount = len(textBeforeStart.splitlines())
- line_count = len(text_before_start.split('\n'))
- if len(text_before_start) > 0 and \
- (text_before_start[-1] == '\n' or text_before_start[-1] == '\r'):
- line_count += 1
- if line_count <= 1:
- self.historyPrev.emit()
- return
- elif event.matches(QKeySequence.MoveToNextPage) or \
- event.matches(QKeySequence.MoveToPreviousPage):
- return self._termWidget.browser().keyPressEvent(event)
- tc = self.textCursor()
- QTextEdit.keyPressEvent(self, event)
- tc.select(QTextCursor.WordUnderCursor)
- cr = self.cursorRect()
- if len(tc.selectedText()) > 0:
- self.completer.setCompletionPrefix(tc.selectedText())
- popup = self.completer.popup()
- popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
- cr.setWidth(self.completer.popup().sizeHintForColumn(0)
- + self.completer.popup().verticalScrollBar().sizeHint().width())
- self.completer.complete(cr)
- else:
- self.completer.popup().hide()
- def sizeHint(self):
- """
- QWidget sizeHint impelemtation
- """
- hint = QTextEdit.sizeHint(self)
- hint.setHeight(self._fittedHeight)
- return hint
- def _fit_to_document(self):
- """
- Update widget height to fit all text
- """
- documentsize = self.document().size().toSize()
- self._fittedHeight = documentsize.height() + (self.height() - self.viewport().height())
- self.setMaximumHeight(self._fittedHeight)
- self.updateGeometry()
- def insertFromMimeData(self, mime_data):
- # Paste only plain text.
- self.insertPlainText(mime_data.text())
- class MyCompleter(QCompleter):
- insertText = pyqtSignal(str)
- def __init__(self, parent=None):
- QCompleter.__init__(self)
- self.setCompletionMode(QCompleter.PopupCompletion)
- self.highlighted.connect(self.setHighlighted)
- def setHighlighted(self, text):
- self.lastSelected = text
- def getSelected(self):
- return self.lastSelected
|