source: main/trunk/plugins/freecad/OpenPLM/openplm.py @ 259

Revision 259, 34.1 KB checked in by pcosquer, 10 years ago (diff)

plugins :fix creation of directories

Line 
1import os
2import shutil
3import json
4import urllib
5import webbrowser
6import tempfile
7
8# poster makes it possible to send http request with files
9# sudo easy_install poster
10from poster.encode import multipart_encode
11from poster.streaminghttp import StreamingHTTPRedirectHandler, StreamingHTTPHandler
12
13import urllib2
14
15
16import PyQt4.QtGui as qt
17from PyQt4 import QtCore
18
19import FreeCAD, FreeCADGui
20
21connect = QtCore.QObject.connect
22
23def main_window():
24    app = qt.qApp
25    for x in app.topLevelWidgets():
26        if type(x) == qt.QMainWindow:
27            return x
28
29def save(gdoc):
30    FreeCADGui.runCommand("Std_Save")
31    gdoc.Label = os.path.splitext(os.path.basename(gdoc.FileName))[0] or gdoc.Label
32   
33def close(gdoc):
34    FreeCADGui.runCommand("Std_CloseActiveWindow")
35    gdoc2 = FreeCAD.ActiveDocument
36    if gdoc == gdoc2:
37        FreeCAD.closeDocument(gdoc.Name)
38
39class OpenPLMPluginInstance(object):
40   
41    #: location of openPLM server
42    SERVER = "http://localhost:8000/"
43    #: OpenPLM main directory
44    OPENPLM_DIR = os.path.expanduser("~/.openplm")
45    #: directory where files are stored
46    PLUGIN_DIR = os.path.join(OPENPLM_DIR, "freecad")
47    #: gedit plugin configuration file
48    CONF_FILE = os.path.join(PLUGIN_DIR, "conf.json")
49
50    def __init__(self):
51       
52        self.opener = urllib2.build_opener(StreamingHTTPHandler(),
53                                           StreamingHTTPRedirectHandler(),
54                                           urllib2.HTTPCookieProcessor())
55        self.opener.addheaders = [('User-agent', 'openplm')]
56        self.username = ""
57        self.connected = False
58        self.documents = {}
59        self.disable_menuitems()
60
61        data = self.get_conf_data()
62        if "server" in data:
63            type(self).SERVER = data["server"]
64
65        try:
66            os.makedirs(self.PLUGIN_DIR, 0700)
67        except os.error:
68            pass
69
70        self.window = main_window()
71
72    def disable_menuitems(self):
73        self.connected = False
74        FreeCADGui.updateGui()
75
76    def enable_menuitems(self):
77        self.connected = True
78        FreeCADGui.updateGui()
79
80    def login(self, username, password):
81        """
82        Open a login dialog and connect the user
83        """
84       
85        self.username = username
86        self.password = password
87
88        data = dict(username=self.username, password=self.password)
89        res = self.get_data("api/login/", data)
90        if res["result"] == "ok":
91            #self._action_group2.set_sensitive(True)
92            self.load_managed_files()
93            self.enable_menuitems()
94        else:
95            self.disable_menuitems()
96            raise ValueError(res["error"])
97
98    def create(self, data, filename, unlock):
99        res = self.get_data("api/create/", data)
100        if not filename:
101            return False, "Bad file name"
102        if res["result"] != "ok":
103            return False, res["error"]
104        else:
105            doc = res["object"]
106            # create a new doc
107            rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
108                               doc["revision"])
109            try:
110                os.makedirs(rep, 0700)
111            except os.error:
112                # directory already exists, just ignores the exception
113                pass
114            gdoc = FreeCAD.ActiveDocument
115            path = os.path.join(rep, filename)
116            gdoc.FileName = path
117            save(gdoc)
118            doc_file = self.upload_file(doc, path)
119            self.add_managed_file(doc, doc_file, path)
120            self.load_file(doc, doc_file["id"], path, gdoc)
121            if not unlock:
122                self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_file["id"]))
123            else:
124                self.send_thumbnail(gdoc)
125                self.forget(gdoc)
126            return True, ""
127
128    def get_data(self, url, data=None, show_errors=True, reraise=False):
129        data_enc = urllib.urlencode(data) if data else None
130        try:
131            return json.load(self.opener.open(self.SERVER + url, data_enc))
132        except urllib2.URLError as e:
133            if show_errors:
134                message = e.reason if hasattr(e, "reason") else ""
135                if not isinstance(message, basestring):
136                    message = str(e)
137                show_error(u"Can not open '%s':>\n\t%s" % \
138                           (url, unicode(message, "utf-8")), self.window)
139            if reraise:
140                raise
141            else:
142                return {"result" : "error", "error" : ""}
143
144    def upload_file(self, doc, path):
145        url = self.SERVER + "api/object/%s/add_file/" % doc["id"]
146        datagen, headers = multipart_encode({"filename": open(path, "rb")})
147        # Create the Request object
148        request = urllib2.Request(url, datagen, headers)
149        res = json.load(self.opener.open(request))
150        return res["doc_file"]
151
152    def download(self, doc, doc_file):
153        f = self.opener.open(self.SERVER + "file/%s/" % doc_file["id"])
154        rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
155                           doc["revision"])
156        try:
157            os.makedirs(rep, 0700)
158        except os.error:
159            # directory already exists, just ignores the exception
160            pass
161        dst_name = os.path.join(rep, doc_file["filename"])
162        dst = open(dst_name, "wb")
163        shutil.copyfileobj(f, dst)
164        f.close()
165        dst.close()
166        self.add_managed_file(doc, doc_file, dst_name)
167        self.load_file(doc, doc_file["id"], dst_name)
168
169    def attach_to_part(self, doc, part_id):
170        res = self.get_data("api/object/%s/attach_to_part/%s/" % (doc["id"], part_id))
171        if res["result"] == "ok":
172            url = self.SERVER + "object/%s/%s/%s/parts" % (doc["type"],
173                    doc["reference"], doc["revision"])
174            webbrowser.open_new_tab(url)
175        else:
176            show_error("Can not attach\n%s" % res.get('error', ''), self.window)
177
178    def check_out(self, doc, doc_file):
179        self.get_data("api/object/%s/checkout/%s/" % (doc["id"], doc_file["id"]))
180        self.download(doc, doc_file)
181
182    def add_managed_file(self, document, doc_file, path):
183        data = self.get_conf_data()
184        documents = data.get("documents", {})
185        doc = documents.get(str(document["id"]), dict(document))
186        files = doc.get("files", {})
187        files[doc_file["id"]] = path
188        doc["files"] = files
189        documents[doc["id"]] = doc
190        data["documents"] = documents
191        self.save_conf(data)
192
193    def save_conf(self, data):
194        f = open(self.CONF_FILE, "w")
195        json.dump(data, f)
196        f.close()
197   
198    def get_conf_data(self):
199        try:
200            with open(self.CONF_FILE, "r") as f:
201                try:
202                    return json.load(f)
203                except ValueError:
204                    # empty/bad config file
205                    return {}
206        except IOError:
207            # file does not exist
208            return {}
209   
210    def set_server(self, url):
211        if not url.endswith("/"):
212            url += "/"
213        data = self.get_conf_data()
214        data["server"] = url
215        type(self).SERVER = url
216        self.save_conf(data)
217        if self.username:
218            try:
219                self.login(self.username, self.password)
220            except ValueError:
221                pass
222
223    def get_managed_files(self):
224        data = self.get_conf_data()
225        files = []
226        for doc in data.get("documents", {}).itervalues():
227            files.extend((d, doc) for d in doc.get("files", {}).items())
228        return files
229   
230    def forget(self, gdoc=None, delete=True, close_doc=False):
231        gdoc = gdoc or FreeCAD.ActiveDocument
232        if gdoc and gdoc in self.documents:
233            doc = self.documents[gdoc]["openplm_doc"]
234            doc_file_id = self.documents[gdoc]["openplm_file_id"]
235            path = self.documents[gdoc]["openplm_path"]
236            del self.documents[gdoc]
237            data = self.get_conf_data()
238            del data["documents"][str(doc["id"])]["files"][str(doc_file_id)]
239            if not  data["documents"][str(doc["id"])]["files"]:
240                del data["documents"][str(doc["id"])]
241            self.save_conf(data)
242            if delete and os.path.exists(path):
243                os.remove(path)
244            if close_doc:
245                close(gdoc)
246
247    def load_managed_files(self):
248        for (doc_file_id, path), doc in self.get_managed_files():
249            self.load_file(doc, doc_file_id, path)
250
251    def load_file(self, doc, doc_file_id, path, gdoc=None):
252        try:
253            document = gdoc or FreeCAD.openDocument(path)
254        except IOError:
255            show_error("Can not load %s" % path, self.window)
256            return
257        self.documents[document] = dict(openplm_doc=doc,
258            openplm_file_id=doc_file_id, openplm_path=path)
259        if " rev. " not in document.Label:
260            document.Label = document.Label + " / %(name)s rev. %(revision)s" % doc
261        return document
262
263    def check_in(self, gdoc, unlock, save_file=True):
264        if gdoc and gdoc in self.documents:
265            doc = self.documents[gdoc]["openplm_doc"]
266            doc_file_id = self.documents[gdoc]["openplm_file_id"]
267            path = self.documents[gdoc]["openplm_path"]
268            def func():
269                # headers contains the necessary Content-Type and Content-Length>
270                # datagen is a generator object that yields the encoded parameters
271                datagen, headers = multipart_encode({"filename": open(path, "rb")})
272                # Create the Request object
273                url = self.SERVER + "api/object/%s/checkin/%s/" % (doc["id"], doc_file_id)
274                request = urllib2.Request(url, datagen, headers)
275                res = self.opener.open(request)
276                if not unlock:
277                    self.get_data("api/object/%s/lock/%s/" % (doc["id"], doc_file_id))
278                else:
279                    self.send_thumbnail(gdoc)
280                    self.forget(gdoc)
281            if save_file:
282                save(gdoc)
283                func()
284            else:
285                func()
286        else:
287            show_error('Can not check in : file not in openPLM', self.window)
288
289    def send_thumbnail(self, gdoc):
290        doc = self.documents[gdoc]["openplm_doc"]
291        doc_file_id = self.documents[gdoc]["openplm_file_id"]
292        view = FreeCADGui.ActiveDocument.ActiveView
293        f = tempfile.NamedTemporaryFile(suffix=".png")
294        view.saveImage(f.name)
295        datagen, headers = multipart_encode({"filename": open(f.name, "rb")})
296        # Create the Request object
297        url = self.SERVER + "api/object/%s/add_thumbnail/%s/" % (doc["id"], doc_file_id)
298        request = urllib2.Request(url, datagen, headers)
299        res = self.opener.open(request)
300        f.close()
301
302    def revise(self, gdoc, revision, unlock):
303        if gdoc and gdoc in self.documents:
304            doc = self.documents[gdoc]["openplm_doc"]
305            doc_file_id = self.documents[gdoc]["openplm_file_id"]
306            path = self.documents[gdoc]["openplm_path"]
307            res = self.get_data("api/object/%s/revise/" % doc["id"],
308                                {"revision" : revision})
309            new_doc = res["doc"]
310            name = os.path.basename(gdoc.FileName)
311            doc_file = None
312            for f in res["files"]:
313                if f["filename"] == name:
314                    doc_file = f
315                    break
316            # create a new doc
317            rep = os.path.join(self.PLUGIN_DIR, doc["type"], doc["reference"],
318                               revision)
319            try:
320                os.makedirs(rep, 0700)
321            except os.error:
322                # directory already exists, just ignores the exception
323                pass
324   
325            self.forget(gdoc, close_doc=False)
326            path = os.path.join(rep, name)
327            gdoc.FileName = path
328            save(gdoc)
329            self.load_file(new_doc, doc_file["id"], path, gdoc)
330            self.add_managed_file(new_doc, doc_file, path)
331            self.check_in(gdoc, unlock, False)
332            self.get_data("api/object/%s/unlock/%s/" % (doc["id"], doc_file_id))       
333        else:
334            show_error("Can not revise : file not in openPLM", self.window)
335
336    def check_is_locked(self, doc_id, file_id, error_dialog=True):
337        """
338        Return True if file which is is *file_id* is locked.
339
340        If it is unlocked and *error_dialog* is True, an ErrorDialog is
341        displayed
342        """
343        locked = self.get_data("api/object/%s/islocked/%s/" % (doc_id, file_id))["locked"]
344        if not locked and error_dialog:
345            show_error("File is not locked, action not allowed", self.window)
346        return locked
347
348
349PLUGIN = OpenPLMPluginInstance()
350
351def show_error(message, parent):
352    dialog = qt.QMessageBox()
353    dialog.setText(message)
354    dialog.setWindowTitle("Error")
355    dialog.setIcon(qt.QMessageBox.Warning)   
356    dialog.exec_()
357
358class Dialog(qt.QDialog):
359
360    TITLE = "..."
361    ACTION_NAME = "..."
362
363    def __init__(self):
364        qt.QDialog.__init__(self)
365        self.setWindowTitle(self.TITLE)
366        self.vbox = qt.QVBoxLayout()
367        self.setLayout(self.vbox)
368        self.instance = PLUGIN
369        self.update_ui()
370
371    def get_value(self, entry, field=None):
372        value = None
373        if isinstance(entry, qt.QLineEdit):
374            value = unicode(entry.text(), "utf-8")
375        elif isinstance(entry, qt.QComboBox):
376            if not field:
377                value = unicode(entry.currentText(), "utf-8")
378            else:
379                value = field["choices"][entry.currentIndex()][0]
380        elif isinstance(entry, qt.QCheckBox):
381            value = entry.isChecked()
382        return value
383
384    def set_value(self, entry, value, field=None):
385        if isinstance(entry, qt.QLineEdit):
386            entry.setText(value or '')
387        elif isinstance(entry, qt.QComboBox):
388            choices = [c[0] for c in field["choices"]]
389            entry.setCurrentIndex(choices.index(value or ''))
390        elif isinstance(entry, qt.QCheckBox):
391            entry.setChecked(value)
392   
393    def field_to_widget(self, field):
394        widget = None
395        attributes = {}
396        if field["type"] in ("text", "int", "decimal", "float"):
397            widget = qt.QLineEdit()
398        elif field["type"] == "boolean":
399            widget = qt.QCheckBox()
400        elif field["type"] == "choice":
401            widget = qt.QComboBox()
402            choices = field["choices"]
403            if [u'', u'---------'] not in choices:
404                choices = ([u'', u'---------'],) + tuple(choices)
405            field["choices"] = choices
406            values = []
407            for _, c in choices:
408                values.append(c)
409            widget.addItems(values)
410        if type == "":
411            raise ValueError()
412        self.set_value(widget, field["initial"], field)
413        return widget
414
415    def update_ui(self):
416        pass
417
418class LoginDialog(Dialog):
419    TITLE = 'Login'
420     
421    def update_ui(self):
422        table = qt.QGridLayout()
423        label = qt.QLabel(self)
424        label.setText('Username:')
425        table.addWidget(label, 0, 0)
426        label = qt.QLabel(self)
427        label.setText('Password:')
428        table.addWidget(label, 1, 0)
429        self.user_entry = qt.QLineEdit(self)
430        table.addWidget(self.user_entry, 0, 1)
431        self.pw_entry = qt.QLineEdit()
432        self.pw_entry.setEchoMode(qt.QLineEdit.Password)
433        table.addWidget(self.pw_entry, 1, 1)
434
435        self.vbox.addLayout(table)
436        buttons = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
437        buttons_box = qt.QDialogButtonBox(buttons, parent=self)
438        connect(buttons_box, QtCore.SIGNAL("accepted()"), self.login)
439        connect(buttons_box, QtCore.SIGNAL("rejected()"), self.reject)
440       
441        self.vbox.addWidget(buttons_box)
442
443    def login(self):
444        username = self.user_entry.text()
445        password = self.pw_entry.text()
446        try:
447            PLUGIN.login(username, password)
448            self.accept()
449        except ValueError, e:
450            self.user_entry.setFocus()
451            show_error("Can not login: %s" % str(e), self)
452
453class ConfigureDialog(Dialog):
454
455    TITLE = "Configure"
456
457    def update_ui(self):
458
459        table = qt.QGridLayout()
460        label = qt.QLabel()
461        label.setText("OpenPLM server's location:")
462        self.url_entry = qt.QLineEdit()
463        self.url_entry.setText(PLUGIN.SERVER)
464        table.addWidget(label, 0, 0)
465        table.addWidget(self.url_entry, 0, 1)
466       
467        self.vbox.addLayout(table)
468        buttons = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
469        buttons_box = qt.QDialogButtonBox(buttons, parent=self)
470        connect(buttons_box, QtCore.SIGNAL("accepted()"), self.action_cb)
471        connect(buttons_box, QtCore.SIGNAL("rejected()"), self.reject)
472        self.vbox.addWidget(buttons_box)
473
474    def action_cb(self):
475        self.accept()
476        url = self.get_value(self.url_entry, None)
477        PLUGIN.set_server(url)
478
479class SearchDialog(Dialog):
480
481    TITLE = "Search"
482    ACTION_NAME = "..."
483    TYPE = "Document"
484    SEARCH_SUFFIX = ""
485    TYPES_URL = "api/docs/"
486    ALL_FILES = False
487    EXPAND_FILES = True
488
489    def update_ui(self):
490       
491        docs = PLUGIN.get_data(self.TYPES_URL)
492        self.types = docs["types"]
493
494        table = qt.QGridLayout()
495        self.vbox.addLayout(table)
496        self.type_entry = qt.QComboBox()
497        self.type_entry.addItems(self.types)
498        self.type_entry.setCurrentIndex(self.types.index(self.TYPE))
499        connect(self.type_entry, QtCore.SIGNAL("activated(const QString&)"),
500                self.type_entry_activate_cb)
501        self.name_entry = qt.QLineEdit()
502        self.rev_entry = qt.QLineEdit()
503        self.fields = [("type", self.type_entry),
504                      ]
505        for i, (text, entry) in enumerate(self.fields):
506            label = qt.QLabel()
507            label.setText(text.capitalize()+":")
508            table.addWidget(label, i, 0)
509            table.addWidget(entry, i, 1)
510       
511        self.advanced_table = qt.QGridLayout()
512        self.advanced_fields = []
513        self.vbox.addLayout(self.advanced_table)
514        self.display_fields(self.TYPE)
515       
516        search_button = qt.QPushButton("Search")
517        connect(search_button, QtCore.SIGNAL("clicked()"), self.search)
518        self.vbox.addWidget(search_button)
519
520        self.results_box = qt.QVBoxLayout()
521        self.vbox.addLayout(self.results_box)
522       
523        self.tree = qt.QTreeWidget()
524        self.tree.setColumnCount(1)
525        self.tree.setHeaderLabel("Results")
526        self.results_box.addWidget(self.tree)
527        connect(self.tree, QtCore.SIGNAL("itemExpanded(QTreeWidgetItem *)"),
528                                         self.expand)
529
530        self.action_button = qt.QPushButton(self.ACTION_NAME)
531        connect(self.action_button, QtCore.SIGNAL("clicked()"), self.action_cb)
532        self.results_box.addWidget(self.action_button)
533
534    def type_entry_activate_cb(self, typename):
535        self.display_fields(typename)
536   
537    def display_fields(self, typename):
538        fields = self.instance.get_data("api/search_fields/%s/" % typename)["fields"]
539        temp = {}
540        for field, entry in self.advanced_fields:
541            temp[field["name"]] = self.get_value(entry, field)
542        while self.advanced_table.count():
543            child = self.advanced_table.itemAt(0).widget()
544            self.advanced_table.removeWidget(child)
545            child.hide()
546            child.destroy()
547        self.advanced_table.invalidate()
548        self.advanced_fields = []
549        for i, field in enumerate(fields):
550            text = field["label"]
551            label = qt.QLabel()
552            label.setText(text.capitalize()+":")
553            self.advanced_table.addWidget(label, i, 0)
554            widget = self.field_to_widget(field)
555            if field["name"] in temp:
556                self.set_value(widget, temp[field["name"]], field)
557            self.advanced_table.addWidget(widget, i, 1)
558            self.advanced_fields.append((field, widget))
559
560    def action_cb(self):
561        node = self.tree.currentItem()
562        if not node:
563            return
564        doc = self.nodes[node.parent()]
565        doc_file = doc["files"][node.parent().indexOfChild(node)]
566        del doc["files"]
567        self.do_action(doc, doc_file)
568
569    def expand(self, item):
570        res = self.nodes[item]
571        suffix = "all/" if self.ALL_FILES else ""
572        url = "api/object/%s/files/%s" % (res["id"], suffix)
573        files = PLUGIN.get_data(url)["files"]
574        if "files" in res:
575            return
576        res["files"] = files
577        item.removeChild(item.child(0))
578        for f in files:
579            node = qt.QTreeWidgetItem([f["filename"]])
580            item.addChild(node)
581        if not files:
582            node = qt.QTreeWidgetItem(["No file attached to document"])
583            item.addChild(node)
584
585    def display_results(self, results):
586        self.nodes = {}
587        self.tree.clear()
588        self.tree.reset()
589        for i, res in enumerate(results):
590            text = "%(reference)s|%(type)s|%(revision)s : %(name)s" % res
591            node = qt.QTreeWidgetItem([text])
592            if self.EXPAND_FILES:
593                child = qt.QTreeWidgetItem(["..."])
594                node.addChild(child)
595            node.setExpanded(False)
596            self.tree.insertTopLevelItem(i, node)
597            self.nodes[node] = res
598
599    def search(self, *args):
600        data = {}
601        for text, entry in self.fields:
602            value = self.get_value(entry, None)
603            if value:
604                data[text] = value
605        for field, entry in self.advanced_fields:
606            value = self.get_value(entry, field)
607            if value:
608                data[field["name"]] = value
609        get = urllib.urlencode(data)
610        self.display_results(PLUGIN.get_data("api/search/%s?%s" % (self.SEARCH_SUFFIX, get))["objects"])
611
612    def do_action(self, doc, doc_file):
613        print doc, doc_file
614
615class CheckOutDialog(SearchDialog):
616    TITLE = "Check-out..."
617    ACTION_NAME = "Check-out"
618
619    def do_action(self, doc, doc_file):
620        PLUGIN.check_out(doc, doc_file)
621        self.accept()
622
623class DownloadDialog(SearchDialog):
624    TITLE = "Download..."
625    ACTION_NAME = "Download"
626    SEARCH_SUFFIX = "false/true/"
627    ALL_FILES = True
628
629    def do_action(self, doc, doc_file):
630        PLUGIN.download(doc, doc_file)
631        self.accept()
632
633class CheckInDialog(Dialog):
634
635    TITLE = "Check-in..."
636    ACTION_NAME = "Check-in"
637
638    def __init__(self, doc, name):
639        self.doc = doc
640        self.name = name
641        Dialog.__init__(self)
642
643    def update_ui(self):
644        text = "%s|%s|%s" % (self.doc["reference"], self.doc["revision"],
645                                       self.doc["type"])
646
647        label = qt.QLabel(text)
648        self.vbox.addWidget(label)
649        self.unlock_button = qt.QCheckBox('Unlock ?')
650        self.vbox.addWidget(self.unlock_button)
651
652        button = qt.QPushButton(self.ACTION_NAME)
653        connect(button, QtCore.SIGNAL("clicked()"), self.action_cb)
654        self.vbox.addWidget(button)
655       
656    def action_cb(self):
657        doc = FreeCAD.ActiveDocument
658        unlock = self.get_value(self.unlock_button, None)
659        PLUGIN.check_in(doc, unlock)
660        self.accept()
661
662class ReviseDialog(Dialog):
663
664    TITLE = "Revise..."
665    ACTION_NAME = "Revise"
666
667    def __init__(self, doc, name, revision):
668        self.doc = doc
669        self.name = name
670        self.revision = revision
671        self.gdoc = None
672        Dialog.__init__(self)
673
674    def update_ui(self):
675        hbox = qt.QHBoxLayout()
676        text = "%s|" % self.doc["reference"]
677        label = qt.QLabel(text)
678        hbox.addWidget(label)
679        self.revision_entry = qt.QLineEdit()
680        self.revision_entry.setText(self.revision)
681        hbox.addWidget(self.revision_entry)
682        text = "|%s" % self.doc["type"]
683        label = qt.QLabel(text)
684        hbox.addWidget(label)
685        self.vbox.addLayout(hbox)
686        self.unlock_button = qt.QCheckBox('Unlock ?')
687        self.vbox.addWidget(self.unlock_button)
688        text = "Warning: old revision file will be automatically unlocked!"
689        label = qt.QLabel(text)
690        self.vbox.addWidget(label)
691
692        button = qt.QPushButton(self.ACTION_NAME)
693        connect(button, QtCore.SIGNAL("clicked()"), self.action_cb)
694        self.vbox.addWidget(button)
695       
696    def action_cb(self):
697        doc = FreeCAD.ActiveDocument
698        self.gdoc = PLUGIN.revise(doc,
699                          self.get_value(self.revision_entry, None),
700                          self.get_value(self.unlock_button, None))
701        self.accept()
702   
703class AttachToPartDialog(SearchDialog):
704    TITLE = "Attach to part"
705    ACTION_NAME = "Attach"
706    SEARCH_SUFFIX = "false/"
707    TYPE = "Part"
708    TYPES_URL = "api/parts/"
709    EXPAND_FILES = False
710
711    def __init__(self, doc):
712        SearchDialog.__init__(self)
713        self.doc = doc
714   
715    def do_action(self, part):
716        PLUGIN.attach_to_part(self.doc, part["id"])
717        self.accept()
718   
719    def action_cb(self):
720        node =  self.tree.currentItem()
721        if not node:
722            return
723        doc = self.nodes[node]
724        self.do_action(doc)
725
726class CreateDialog(SearchDialog):
727
728    TITLE = "Create a document..."
729    ACTION_NAME = "Create"
730    TYPE = "Document"
731    TYPES_URL = "api/docs/"
732
733    def update_ui(self):
734        self.doc_created = None       
735        docs = PLUGIN.get_data(self.TYPES_URL)
736        self.types = docs["types"]
737
738        table = qt.QGridLayout()
739        self.vbox.addLayout(table)
740        self.type_entry = qt.QComboBox()
741        self.type_entry.addItems(self.types)
742        self.type_entry.setCurrentIndex(self.types.index(self.TYPE))
743        connect(self.type_entry, QtCore.SIGNAL("activated(const QString&)"),
744                self.type_entry_activate_cb)
745        self.fields = [("type", self.type_entry),
746                      ]
747        for i, (text, entry) in enumerate(self.fields):
748            label = qt.QLabel()
749            label.setText(text.capitalize()+":")
750            table.addWidget(label, i, 0)
751            table.addWidget(entry, i, 1)
752       
753        self.advanced_table = qt.QGridLayout()
754        self.advanced_fields = []
755        self.vbox.addLayout(self.advanced_table)
756        self.display_fields(self.TYPE)
757       
758        hbox = qt.QHBoxLayout()
759        label = qt.QLabel()
760        label.setText("Filename:")
761        hbox.addWidget(label)
762        self.filename_entry = qt.QLineEdit()
763        hbox.addWidget(self.filename_entry)
764        doc = FreeCAD.ActiveDocument
765        self.filename_entry.setText(os.path.basename(doc.FileName) or "x.fcstd")
766        self.vbox.addLayout(hbox)
767        self.unlock_button = qt.QCheckBox('Unlock ?')
768        self.vbox.addWidget(self.unlock_button)
769               
770        self.action_button = qt.QPushButton(self.ACTION_NAME)
771        connect(self.action_button, QtCore.SIGNAL("clicked()"), self.action_cb)
772        self.vbox.addWidget(self.action_button)
773
774    def type_entry_activate_cb(self, typename):
775        self.display_fields(typename)
776   
777    def display_fields(self, typename):
778        fields = self.instance.get_data("api/creation_fields/%s/" % typename)["fields"]
779        temp = {}
780        for field, entry in self.advanced_fields:
781            temp[field["name"]] = self.get_value(entry, field)
782        while self.advanced_table.count():
783            child = self.advanced_table.itemAt(0).widget()
784            self.advanced_table.removeWidget(child)
785            child.hide()
786            child.destroy()
787        self.advanced_table.invalidate()
788        self.advanced_fields = []
789        for i, field in enumerate(fields):
790            text = field["label"]
791            label = qt.QLabel()
792            label.setText(text.capitalize()+":")
793            self.advanced_table.addWidget(label, i, 0)
794            widget = self.field_to_widget(field)
795            if field["name"] in temp:
796                self.set_value(widget, temp[field["name"]], field)
797            self.advanced_table.addWidget(widget, i, 1)
798            self.advanced_fields.append((field, widget))
799
800    def action_cb(self):
801        b, error = PLUGIN.create(self.get_creation_data(),
802                                 self.get_value(self.filename_entry, None),
803                                 self.get_value(self.unlock_button, None))
804        if b:
805            self.accept()
806        else:
807            show_error(error, self)
808        self.doc_created = b
809
810    def get_creation_data(self):
811        data = {}
812        for field_name, widget in self.fields:
813            data[field_name] = self.get_value(widget, None)
814        for field, widget in self.advanced_fields:
815            data[field["name"]] = self.get_value(widget, field)
816        return data
817
818
819class OpenPLMLogin:
820
821    def Activated(self):
822        dialog = LoginDialog()
823        dialog.exec_()
824
825    def GetResources(self):
826        return {'Pixmap' : 'plop.png', 'MenuText': 'Login', 'ToolTip': 'Login'}
827
828FreeCADGui.addCommand('OpenPLM_Login', OpenPLMLogin())
829
830class OpenPLMConfigure:
831
832    def Activated(self):
833        dialog = ConfigureDialog()
834        dialog.exec_()
835
836    def GetResources(self):
837        return {'Pixmap' : 'plop.png', 'MenuText': 'Configure', 'ToolTip': 'Configure'}
838
839FreeCADGui.addCommand('OpenPLM_Configure', OpenPLMConfigure())
840
841class OpenPLMCheckOut:
842
843    def Activated(self):
844        dialog = CheckOutDialog()
845        dialog.exec_()
846   
847    def GetResources(self):
848        return {'Pixmap' : 'plop.png', 'MenuText': 'Check-out', 'ToolTip': 'Check-out'}
849   
850    def IsActive(self):
851        return PLUGIN.connected
852
853FreeCADGui.addCommand('OpenPLM_CheckOut', OpenPLMCheckOut())
854
855
856class OpenPLMDownload:
857   
858    def Activated(self):
859        dialog = DownloadDialog()
860        dialog.exec_()
861
862    def GetResources(self):
863        return {'Pixmap' : 'plop.png', 'MenuText': 'Download', 'ToolTip': 'Download'}
864   
865    def IsActive(self):
866        return PLUGIN.connected
867
868FreeCADGui.addCommand('OpenPLM_Download', OpenPLMDownload())
869
870class OpenPLMForget:
871
872    def Activated(self):
873        PLUGIN.forget(close_doc=True)
874
875    def GetResources(self):
876        return {'Pixmap' : 'plop.png', 'MenuText': 'Forget current file',
877                'ToolTip': 'Forget and delete current file'}
878
879    def IsActive(self):
880        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
881
882FreeCADGui.addCommand('OpenPLM_Forget', OpenPLMForget())
883
884class OpenPLMCheckIn:
885   
886    def Activated(self):
887        gdoc = FreeCAD.ActiveDocument
888        if gdoc and gdoc in PLUGIN.documents:
889            doc = PLUGIN.documents[gdoc]["openplm_doc"]
890            doc_file_id = PLUGIN.documents[gdoc]["openplm_file_id"]
891            path = PLUGIN.documents[gdoc]["openplm_path"]
892            if not doc or not PLUGIN.check_is_locked(doc["id"], doc_file_id):
893                return
894            name = os.path.basename(path)
895            dialog = CheckInDialog(doc, name)
896            dialog.exec_()
897            if gdoc not in PLUGIN.documents:
898                close(gdoc)
899        else:
900            win = main_window()
901            show_error("Document not stored in OpenPLM", win)
902
903    def GetResources(self):
904        return {'Pixmap' : 'plop.png', 'MenuText': 'Check-in', 'ToolTip': 'Check-in'}
905
906    def IsActive(self):
907        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
908
909FreeCADGui.addCommand('OpenPLM_CheckIn', OpenPLMCheckIn())
910
911class OpenPLMRevise:
912
913    def Activated(self):
914        gdoc = FreeCAD.ActiveDocument
915        if gdoc and gdoc in PLUGIN.documents:
916            doc = PLUGIN.documents[gdoc]["openplm_doc"]
917            doc_file_id = PLUGIN.documents[gdoc]["openplm_file_id"]
918            path = PLUGIN.documents[gdoc]["openplm_path"]
919            if not doc or not PLUGIN.check_is_locked(doc["id"], doc_file_id):
920                return
921            revisable = PLUGIN.get_data("api/object/%s/isrevisable/" % doc["id"])["revisable"]
922            if not revisable:
923                win = main_window()
924                show_error("Document can not be revised", win)
925                return
926            res = PLUGIN.get_data("api/object/%s/nextrevision/" % doc["id"])
927            revision = res["revision"]
928            name = os.path.basename(path)
929            dialog = ReviseDialog(doc, name, revision)
930            dialog.exec_()
931            if gdoc not in PLUGIN.documents:
932                close(gdoc)
933        else:
934            win = main_window()
935            show_error("Document not stored in OpenPLM", win)
936
937    def GetResources(self):
938        return {'Pixmap' : 'plop.png', 'MenuText': 'Revise', 'ToolTip': 'Revise'}
939   
940    def IsActive(self):
941        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
942
943FreeCADGui.addCommand('OpenPLM_Revise', OpenPLMRevise())
944
945class OpenPLMAttach:
946
947    def Activated(self):
948        gdoc = FreeCAD.ActiveDocument
949        if gdoc and gdoc in PLUGIN.documents:
950            doc = PLUGIN.documents[gdoc]["openplm_doc"]
951            dialog = AttachToPartDialog(doc)
952            dialog.exec_()
953        else:
954            win = main_window()
955            show_error("Document not stored in OpenPLM", win)
956
957    def GetResources(self):
958        return {'Pixmap' : 'plop.png', 'MenuText': 'Attach to a part',
959                'ToolTip': 'Attach to a part'}
960   
961    def IsActive(self):
962        return PLUGIN.connected and FreeCAD.ActiveDocument in PLUGIN.documents
963
964FreeCADGui.addCommand('OpenPLM_AttachToPart', OpenPLMAttach())
965
966class OpenPLMCreate:
967   
968    def Activated(self):
969        gdoc = FreeCAD.ActiveDocument
970        win = main_window()
971        if not gdoc:
972            show_error("Need an opened file to create a document", win)
973            return
974        if gdoc in PLUGIN.documents:
975            show_error("Current file already attached to a document", win)
976            return
977        dialog = CreateDialog()
978        resp = dialog.exec_()
979        if dialog.doc_created and gdoc not in PLUGIN.documents:
980            close(gdoc)
981
982    def GetResources(self):
983        return {'Pixmap' : 'plop.png', 'MenuText': 'Create a Document',
984                'ToolTip': 'Create a document'}
985   
986    def IsActive(self):
987        doc = FreeCAD.ActiveDocument
988        return PLUGIN.connected and doc and doc not in PLUGIN.documents
989
990FreeCADGui.addCommand('OpenPLM_Create', OpenPLMCreate())
991
992def build_menu():
993    win = main_window()
994    mb =  win.menuBar()
995    menu = mb.addMenu("OpenPLM")
996    for cls in (OpenPLMLogin, OpenPLMCheckOut):
997        cmd = cls()
998        action = qt.QAction(cmd.GetResources()["MenuText"], None)
999        QtCore.QObject.connect(action, QtCore.SIGNAL("triggered"), cmd.Activated)
1000        menu.addAction(action)
1001    menu.show()
1002
Note: See TracBrowser for help on using the repository browser.