~bzr/qbzr/daily-ppa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# -*- coding: utf-8 -*-
#
# QBzr - Qt frontend to Bazaar commands
#
# Contributor:
#   Alexander Belchenko, 2009
#
# 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.

"""Dialog to run arbitrary bzr command."""

import os
from PyQt4 import QtCore, QtGui

from bzrlib import osutils

from bzrlib.plugins.qbzr.lib import MS_WINDOWS
from bzrlib.plugins.qbzr.lib.help import get_help_topic_as_html
from bzrlib.plugins.qbzr.lib.i18n import gettext
from bzrlib.plugins.qbzr.lib.subprocess import SubProcessDialog
from bzrlib.plugins.qbzr.lib.ui_run import Ui_RunDialog
from bzrlib.plugins.qbzr.lib.util import (
    hookup_directory_picker,
    shlex_split_unicode,
    )


class QBzrRunDialog(SubProcessDialog):

    def __init__(self, command=None, parameters=None, workdir=None,
        category=None, ui_mode=False, parent=None, execute=False):
        """Build dialog.

        @param command: initial command selection.
        @param parameters: initial options and arguments (string) for command.
        @param workdir: working directory to run command.
        @param category: initial category selection.
        @param ui_mode: wait after the operation is complete.
        @param parent:  parent window.
        @param execute: True => run command immediately
        """
        super(QBzrRunDialog, self).__init__(name="run", ui_mode=ui_mode,
            dialog=True, parent=parent)
        self.ui = Ui_RunDialog()
        self.ui.setupUi(self)
        if workdir is None:
            workdir = osutils.getcwd()
        self.ui.wd_edit.setText(workdir)
        # set help_browser with some default text
        self.set_default_help()
        # cmd_combobox should fill all available space
        self.ui.cmd_layout.setColumnStretch(1, 1)
        self.ui.cmd_layout.setColumnStretch(2, 1)
        # fill cmd_combobox with available commands
        self.collect_command_names()
        categories = sorted(self.all_cmds.keys())
        self.ui.cat_combobox.insertItems(0, categories)
        self.set_cmd_combobox(cmd_name=command)
        # add the parameters, if any
        if parameters:
            self.ui.opt_arg_edit.setText(parameters)
        
        # and add the subprocess widgets
        for w in self.make_default_layout_widgets():
            self.ui.subprocess_container_layout.addWidget(w)
        self.process_widget.hide_progress()
        
        # restore the sizes
        self.restoreSize("run", None)
        self.splitter = self.ui.splitter
        self.restoreSplitterSizes()
        # setup signals
        QtCore.QObject.connect(self.ui.hidden_checkbox,
            QtCore.SIGNAL("stateChanged(int)"),
            self.set_show_hidden)
        QtCore.QObject.connect(self.ui.cmd_combobox,
            QtCore.SIGNAL("currentIndexChanged(const QString&)"),
            self.set_cmd_help)
        QtCore.QObject.connect(self.ui.cmd_combobox,
            QtCore.SIGNAL("editTextChanged(const QString&)"),
            self.set_cmd_help)
        QtCore.QObject.connect(self.ui.cat_combobox,
            QtCore.SIGNAL("currentIndexChanged(const QString&)"),
            self.set_category)
        hookup_directory_picker(self, self.ui.browse_button, 
            self.ui.wd_edit, gettext("Select working directory"))
        QtCore.QObject.connect(self.ui.directory_button,
            QtCore.SIGNAL("clicked()"),
            self.insert_directory)
        QtCore.QObject.connect(self.ui.filenames_button,
            QtCore.SIGNAL("clicked()"),
            self.insert_filenames)
        # Init the category if set.
        # (This needs to be done after the signals are hooked up)
        if category:
            cb = self.ui.cat_combobox
            index = cb.findText(category)
            if index >= 0:
                cb.setCurrentIndex(index)
        # ready to go
        if execute:
            # hide user edit fields
            self.ui.run_container.hide()
            self.ui.help_browser.hide()
            
            # create edit button
            self.editButton = QtGui.QPushButton(gettext('&Edit'))
            QtCore.QObject.connect(self.editButton,
                QtCore.SIGNAL("clicked()"),
                self.enable_command_edit)

            # cause edit button to be shown if command fails
            QtCore.QObject.connect(self,
                                   QtCore.SIGNAL("subprocessFailed(bool)"),
                                   self.editButton,
                                   QtCore.SLOT("setHidden(bool)"))

            # add edit button to dialog buttons
            self.buttonbox.addButton(self.editButton,
                QtGui.QDialogButtonBox.ResetRole)
            
            # setup initial dialog button status
            self.closeButton.setHidden(True)
            self.okButton.setHidden(True)
            self.editButton.setHidden(True)
            
            # cancel button gets hidden when finished.
            QtCore.QObject.connect(self,
                               QtCore.SIGNAL("subprocessFinished(bool)"),
                               self.cancelButton,
                               QtCore.SLOT("setHidden(bool)"))
            
            # run command
            self.do_start()
        else:
            if command:
                self.ui.opt_arg_edit.setFocus()
            else:
                self.ui.cmd_combobox.setFocus()

    def enable_command_edit(self):
        """Hide Edit button and make user edit fields visible"""
        self.editButton.setHidden(True)
        QtCore.QObject.disconnect(self,
                                  QtCore.SIGNAL("subprocessFailed(bool)"),
                                  self.editButton,
                                  QtCore.SLOT("setHidden(bool)"))
        self.ui.run_container.show()
        self.ui.help_browser.show()
        self.okButton.setShown(True)

    def set_default_help(self):
        """Set default text in help widget."""
        self.ui.help_browser.setHtml("<i><small>%s</small></i>" % 
            gettext("Help for command"))

    def collect_command_names(self):
        """Collect names of available bzr commands."""
        from bzrlib import commands as _mod_commands
        names = list(_mod_commands.all_command_names())
        self.cmds_dict = dict((n, _mod_commands.get_cmd_object(n)) 
                              for n in names)
        # Find the commands for each category, public or otherwise
        builtins = _mod_commands.builtin_command_names()
        self.all_cmds = {'All': []}
        self.public_cmds = {'All': []}
        for name, cmd in self.cmds_dict.iteritems():
            # If a command is builtin, we always put it into the Core
            # category, even if overridden in a plugin
            if name in builtins:
                category = 'Core'
            else:
                category = cmd.plugin_name()
            self.all_cmds['All'].append(name)
            self.all_cmds.setdefault(category, []).append(name)
            if not cmd.hidden:
                self.public_cmds['All'].append(name)
                self.public_cmds.setdefault(category, []).append(name)
        # Sort them
        for category in self.all_cmds:
            self.all_cmds[category].sort()
            try:
                self.public_cmds[category].sort()
            except KeyError:
                # no public commands - that's ok
                pass

    def set_category(self, category):
        cmd_name = self._get_cmd_name()
        all = self.ui.hidden_checkbox.isChecked()
        category = unicode(category)
        self.set_cmd_combobox(cmd_name=cmd_name, all=all, category=category)

    def set_show_hidden(self, show):
        cmd_name = self._get_cmd_name()
        all = bool(show)
        category = unicode(self.ui.cat_combobox.currentText())
        self.set_cmd_combobox(cmd_name=cmd_name, all=all, category=category)

    def set_cmd_combobox(self, cmd_name=None, all=False, category=None):
        """Fill command combobox with bzr commands names.

        @param cmd_name: if not None, the command to initially select
            if it exists in the list.
        @param all: show all commands including hidden ones.
        @param category: show commands just for this category.
            If None, commands in all categories are shown.
        """
        cb = self.ui.cmd_combobox
        cb.clear()
        if all:
            lookup = self.all_cmds
        else:
            lookup = self.public_cmds
        if category is None:
            category = 'All'
        cb.addItems(lookup.get(category, []))
        if cmd_name is None:
            index = -1
        else:
            index = cb.findText(cmd_name)
            if index >= 0:
                self.set_cmd_help(cmd_name)
        cb.setCurrentIndex(index)    

    def _get_cmd_name(self):
        """Return the command name."""
        return unicode(self.ui.cmd_combobox.currentText()).strip()

    def set_cmd_help(self, cmd_name):
        """Show help for selected command in help widget.

        @param cmd_name: name of command to show help.
        """
        cmd_name = unicode(cmd_name)
        # XXX handle command aliases???
        cmd_object = self.cmds_dict.get(cmd_name)
        if cmd_object:
            self.ui.help_browser.setHtml(
                get_help_topic_as_html("commands/" + cmd_name))
        else:
            self.set_default_help()

    def _get_cwd(self, default=None):
        """Return selected working dir for command.

        @param default: if working dir is not exists then return this default 
            value.
        """
        cwd = unicode(self.ui.wd_edit.text())
        if not os.path.isdir(cwd):
            cwd = default
        return cwd

    def _prepare_filepath(self, path):
        """Ensure path is safe to insert to options/arguments command line.

        On Windows convert backslashes to slashes;
        if path contains spaces we need to quote it.
        @return: path string suitable to insert to command line.
        """
        if MS_WINDOWS:
            path = path.replace('\\', '/')
        if " " in path:
            path = '"%s"' % path
        return path

    def insert_directory(self):
        """Select existing directory and insert it to command line."""
        cwd = self._get_cwd("")
        path = QtGui.QFileDialog.getExistingDirectory(self,
            gettext("Select path to insert"),
            cwd)
        if path:
            self.ui.opt_arg_edit.insert(
                self._prepare_filepath(unicode(path))+" ")

    def insert_filenames(self):
        """Select one or more existing files and insert them to command line."""
        cwd = self._get_cwd("")
        filenames = QtGui.QFileDialog.getOpenFileNames(self,
            gettext("Select files to insert"),
            cwd)
        for i in filenames:
            self.ui.opt_arg_edit.insert(
                self._prepare_filepath(unicode(i))+" ")

    def validate(self):
        """Validate before launch command: start command only if there is one."""
        if self._get_cmd_name():
            return True
        return False

    def do_start(self):
        """Launch command."""
        cwd = self._get_cwd()
        args = [self._get_cmd_name()]
        opt_arg = unicode(self.ui.opt_arg_edit.text())
        args.extend(shlex_split_unicode(opt_arg))
        self.process_widget.do_start(cwd, *args)

    def _saveSize(self, config):
        SubProcessDialog._saveSize(self, config)
        self._saveSplitterSizes(config, self.splitter)