requirements: install newer versions of send2trash
[git-cola.git] / cola / widgets / archive.py
blob53977b648868d19f5313ba2bfbff67285aac0130
1 """Git Archive dialog"""
2 import os
4 from qtpy import QtCore
5 from qtpy import QtWidgets
6 from qtpy.QtCore import Qt
7 from qtpy.QtCore import Signal
9 from ..git import STDOUT
10 from ..i18n import N_
11 from ..interaction import Interaction
12 from .. import cmds
13 from .. import core
14 from .. import icons
15 from .. import qtutils
16 from .text import LineEdit
17 from .standard import Dialog
18 from . import defs
21 class ExpandableGroupBox(QtWidgets.QGroupBox):
22 expanded = Signal(bool)
24 def __init__(self, parent=None):
25 QtWidgets.QGroupBox.__init__(self, parent)
26 self.setFlat(True)
27 self.is_expanded = True
28 self.click_pos = None
29 self.arrow_icon_size = defs.small_icon
31 def set_expanded(self, expanded):
32 if expanded == self.is_expanded:
33 self.expanded.emit(expanded)
34 return
35 self.is_expanded = expanded
36 for widget in self.findChildren(QtWidgets.QWidget):
37 widget.setHidden(not expanded)
38 self.expanded.emit(expanded)
40 def mousePressEvent(self, event):
41 if event.button() == Qt.LeftButton:
42 option = QtWidgets.QStyleOptionGroupBox()
43 self.initStyleOption(option)
44 icon_size = defs.small_icon
45 button_area = QtCore.QRect(0, 0, icon_size, icon_size)
46 offset = icon_size + defs.spacing
47 adjusted = option.rect.adjusted(0, 0, -offset, 0)
48 top_left = adjusted.topLeft()
49 button_area.moveTopLeft(QtCore.QPoint(top_left))
50 self.click_pos = event.pos()
51 QtWidgets.QGroupBox.mousePressEvent(self, event)
53 def mouseReleaseEvent(self, event):
54 if event.button() == Qt.LeftButton and self.click_pos == event.pos():
55 self.set_expanded(not self.is_expanded)
56 QtWidgets.QGroupBox.mouseReleaseEvent(self, event)
58 def paintEvent(self, _event):
59 painter = QtWidgets.QStylePainter(self)
60 option = QtWidgets.QStyleOptionGroupBox()
61 self.initStyleOption(option)
62 painter.save()
63 painter.translate(self.arrow_icon_size + defs.spacing, 0)
64 painter.drawText(option.rect, Qt.AlignLeft, self.title())
65 painter.restore()
67 style = QtWidgets.QStyle
68 point = option.rect.adjusted(0, -4, 0, 0).topLeft()
69 icon_size = self.arrow_icon_size
70 option.rect = QtCore.QRect(point.x(), point.y(), icon_size, icon_size)
71 if self.is_expanded:
72 painter.drawPrimitive(style.PE_IndicatorArrowDown, option)
73 else:
74 painter.drawPrimitive(style.PE_IndicatorArrowRight, option)
77 def save_archive(context):
78 oid = context.git.rev_parse('HEAD')[STDOUT]
79 show_save_dialog(context, oid, parent=qtutils.active_window())
82 def show_save_dialog(context, oid, parent=None):
83 shortoid = oid[:7]
84 dlg = Archive(context, oid, shortoid, parent=parent)
85 dlg.show()
86 if dlg.exec_() != dlg.Accepted:
87 return None
88 return dlg
91 class Archive(Dialog):
92 def __init__(self, context, ref, shortref=None, parent=None):
93 Dialog.__init__(self, parent=parent)
94 self.context = context
95 if parent is not None:
96 self.setWindowModality(Qt.WindowModal)
98 # input
99 self.ref = ref
100 if shortref is None:
101 shortref = ref
103 # outputs
104 self.fmt = None
106 filename = f'{os.path.basename(core.getcwd())}-{shortref}'
107 self.prefix = filename + '/'
108 self.filename = filename
110 # widgets
111 self.setWindowTitle(N_('Save Archive'))
113 self.filetext = LineEdit()
114 self.filetext.set_value(self.filename)
116 self.browse = qtutils.create_toolbutton(icon=icons.file_zip())
118 stdout = context.git.archive('--list')[STDOUT]
119 self.format_strings = stdout.rstrip().splitlines()
120 self.format_combo = qtutils.combo(self.format_strings)
122 self.close_button = qtutils.close_button()
123 self.save_button = qtutils.create_button(
124 text=N_('Save'), icon=icons.save(), default=True
126 self.prefix_label = QtWidgets.QLabel()
127 self.prefix_label.setText(N_('Prefix'))
128 self.prefix_text = LineEdit()
129 self.prefix_text.set_value(self.prefix)
131 self.prefix_group = ExpandableGroupBox()
132 self.prefix_group.setTitle(N_('Advanced'))
134 # layouts
135 self.filelayt = qtutils.hbox(
136 defs.no_margin, defs.spacing, self.browse, self.filetext, self.format_combo
139 self.prefixlayt = qtutils.hbox(
140 defs.margin, defs.spacing, self.prefix_label, self.prefix_text
142 self.prefix_group.setLayout(self.prefixlayt)
143 self.prefix_group.set_expanded(False)
145 self.btnlayt = qtutils.hbox(
146 defs.no_margin,
147 defs.spacing,
148 qtutils.STRETCH,
149 self.close_button,
150 self.save_button,
153 self.mainlayt = qtutils.vbox(
154 defs.margin,
155 defs.no_spacing,
156 self.filelayt,
157 self.prefix_group,
158 qtutils.STRETCH,
159 self.btnlayt,
161 self.setLayout(self.mainlayt)
163 # initial setup; done before connecting to avoid
164 # signal/slot side-effects
165 if 'tar.gz' in self.format_strings:
166 idx = self.format_strings.index('tar.gz')
167 elif 'zip' in self.format_strings:
168 idx = self.format_strings.index('zip')
169 else:
170 idx = 0
171 self.format_combo.setCurrentIndex(idx)
172 self.update_format(idx)
174 # connections
175 # pylint: disable=no-member
176 self.filetext.textChanged.connect(self.filetext_changed)
177 self.prefix_text.textChanged.connect(self.prefix_text_changed)
178 self.format_combo.currentIndexChanged.connect(self.update_format)
179 self.prefix_group.expanded.connect(self.prefix_group_expanded)
180 self.accepted.connect(self.archive_saved)
182 qtutils.connect_button(self.browse, self.choose_filename)
183 qtutils.connect_button(self.close_button, self.reject)
184 qtutils.connect_button(self.save_button, self.save_archive)
186 self.init_size(parent=parent)
188 def archive_saved(self):
189 context = self.context
190 ref = self.ref
191 fmt = self.fmt
192 prefix = self.prefix
193 filename = self.filename
195 cmds.do(cmds.Archive, context, ref, fmt, prefix, filename)
196 Interaction.information(
197 N_('File Saved'), N_('File saved to "%s"') % self.filename
200 def save_archive(self):
201 filename = self.filename
202 if not filename:
203 return
204 if core.exists(filename):
205 title = N_('Overwrite File?')
206 msg = N_('The file "%s" exists and will be overwritten.') % filename
207 info_txt = N_('Overwrite "%s"?') % filename
208 ok_txt = N_('Overwrite')
209 if not Interaction.confirm(
210 title, msg, info_txt, ok_txt, default=False, icon=icons.save()
212 return
213 self.accept()
215 def choose_filename(self):
216 filename = qtutils.save_as(self.filename)
217 if not filename:
218 return
219 self.filetext.setText(filename)
220 self.update_format(self.format_combo.currentIndex())
222 def filetext_changed(self, filename):
223 self.filename = filename
224 self.save_button.setEnabled(bool(self.filename))
225 prefix = self.strip_exts(os.path.basename(self.filename)) + '/'
226 self.prefix_text.setText(prefix)
228 def prefix_text_changed(self, prefix):
229 self.prefix = prefix
231 def strip_exts(self, text):
232 for format_string in self.format_strings:
233 ext = '.' + format_string
234 if text.endswith(ext):
235 return text[: -len(ext)]
236 return text
238 def update_format(self, idx):
239 self.fmt = self.format_strings[idx]
240 text = self.strip_exts(self.filetext.text())
241 self.filename = f'{text}.{self.fmt}'
242 self.filetext.setText(self.filename)
243 self.filetext.setFocus()
244 if '/' in text:
245 start = text.rindex('/') + 1
246 else:
247 start = 0
248 self.filetext.setSelection(start, len(text) - start)
250 def prefix_group_expanded(self, expanded):
251 if expanded:
252 self.prefix_text.setFocus()
253 else:
254 self.filetext.setFocus()